@consilioweb/payload-support 0.10.0 → 0.11.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 (42) hide show
  1. package/dist/components/TicketConversation/locales/en.json +25 -1
  2. package/dist/components/TicketConversation/locales/fr.json +25 -1
  3. package/dist/index.cjs +799 -4
  4. package/dist/index.d.cts +15 -1
  5. package/dist/index.d.ts +15 -1
  6. package/dist/index.js +800 -6
  7. package/dist/styles/TicketDetail.module.scss +294 -0
  8. package/dist/views/TicketDetailView/client.cjs +455 -249
  9. package/dist/views/TicketDetailView/client.js +456 -250
  10. package/dist/views/TicketInboxView/client.cjs +10 -9
  11. package/dist/views/TicketInboxView/client.js +2 -1
  12. package/package.json +1 -1
  13. package/src/collections/ClientSummaries.ts +16 -0
  14. package/src/collections/TicketCollaborators.ts +93 -0
  15. package/src/collections/TicketFeedback.ts +116 -0
  16. package/src/collections/TicketMessages.ts +9 -0
  17. package/src/collections/Tickets.ts +31 -1
  18. package/src/collections/index.ts +2 -0
  19. package/src/components/TicketConversation/locales/en.json +25 -1
  20. package/src/components/TicketConversation/locales/fr.json +25 -1
  21. package/src/endpoints/escalate.ts +44 -0
  22. package/src/endpoints/index.ts +15 -0
  23. package/src/endpoints/invite-collaborator.ts +215 -0
  24. package/src/endpoints/kb-search.ts +156 -0
  25. package/src/endpoints/ticket-feedback.ts +104 -0
  26. package/src/endpoints/transfer-ticket.ts +248 -0
  27. package/src/index.ts +1 -0
  28. package/src/plugin.ts +4 -0
  29. package/src/portal/auth/ChatWidget.tsx +11 -0
  30. package/src/portal/auth/dashboard/DashboardClient.tsx +85 -99
  31. package/src/portal/auth/dashboard/page.tsx +11 -2
  32. package/src/portal/auth/tickets/detail/TransferAndInviteActions.tsx +402 -0
  33. package/src/portal/auth/tickets/detail/page.tsx +122 -23
  34. package/src/portal/auth/tickets/new/KbDeflection.tsx +128 -0
  35. package/src/portal/auth/tickets/new/page.tsx +203 -100
  36. package/src/portal/locales/en.json +5 -0
  37. package/src/portal/locales/fr.json +5 -0
  38. package/src/styles/TicketDetail.module.scss +294 -0
  39. package/src/types.ts +19 -0
  40. package/src/utils/slugs.ts +2 -0
  41. package/src/views/TicketDetailView/client.tsx +346 -89
  42. package/src/views/TicketInboxView/client.tsx +2 -1
@@ -18,6 +18,10 @@ var React__default = /*#__PURE__*/_interopDefault(React);
18
18
  var Link__default = /*#__PURE__*/_interopDefault(Link);
19
19
  var s__default = /*#__PURE__*/_interopDefault(s);
20
20
 
21
+ const ALIASES = [
22
+ { value: "Support Consilioweb", label: "En tant que : Support Consilioweb" },
23
+ { value: "contact@consilioweb.fr", label: "En tant que : contact@consilioweb.fr" }
24
+ ];
21
25
  const STATUS_STYLE = {
22
26
  open: { bg: "#dbeafe", color: "#1e40af" },
23
27
  waiting_client: { bg: "#fef3c7", color: "#92400e" },
@@ -119,6 +123,34 @@ const RewriteDropdown = ({ disabled, loading, onSelect, toolbarBtnClass }) => {
119
123
  )) })
120
124
  ] });
121
125
  };
126
+ function NextActionItem({ action, index, onToggle }) {
127
+ const [pending, setPending] = React.useState(false);
128
+ const labelMap = { now: "Maintenant", today: "Aujourd'hui", "this-week": "Cette semaine" };
129
+ const done = !!action.done;
130
+ return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: s__default.default.aiCardNextAction, children: [
131
+ /* @__PURE__ */ jsxRuntime.jsx(
132
+ "button",
133
+ {
134
+ type: "button",
135
+ className: `${s__default.default.aiCardNextCheck} ${done ? s__default.default.aiCardNextCheckDone : ""}`,
136
+ onClick: async () => {
137
+ setPending(true);
138
+ try {
139
+ await onToggle(!done);
140
+ } finally {
141
+ setPending(false);
142
+ }
143
+ },
144
+ "aria-pressed": done,
145
+ "aria-label": done ? "Marquer non fait" : "Marquer fait",
146
+ disabled: pending,
147
+ children: done && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, children: "\u2713" })
148
+ }
149
+ ),
150
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${s__default.default.aiCardNextLabel} ${done ? s__default.default.aiCardNextLabelDone : ""}`, children: action.label }),
151
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.aiCardNextWhen, children: labelMap[action.priority] || action.priority })
152
+ ] });
153
+ }
122
154
  const TicketDetailClient = () => {
123
155
  const { t } = useTranslation.useTranslation();
124
156
  const searchParams = navigation.useSearchParams();
@@ -140,6 +172,7 @@ const TicketDetailClient = () => {
140
172
  const [isInternal, setIsInternal] = React.useState(false);
141
173
  const [notifyClient, setNotifyClient] = React.useState(true);
142
174
  const [sendAsClient, setSendAsClient] = React.useState(false);
175
+ const [fromAlias, setFromAlias] = React.useState("");
143
176
  const [editingMsgId, setEditingMsgId] = React.useState(null);
144
177
  const [editingBody, setEditingBody] = React.useState("");
145
178
  const [editingHtml, setEditingHtml] = React.useState("");
@@ -149,10 +182,20 @@ const TicketDetailClient = () => {
149
182
  const [showMenu, setShowMenu] = React.useState(false);
150
183
  const [clientTyping, setClientTyping] = React.useState(false);
151
184
  const [aiReplying, setAiReplying] = React.useState(false);
185
+ const [aiSuggestion, setAiSuggestion] = React.useState(null);
152
186
  const [aiRewriting, setAiRewriting] = React.useState(false);
153
187
  const [sentiment, setSentiment] = React.useState(null);
154
188
  const [statusUpdating, setStatusUpdating] = React.useState(false);
155
- const [showActivity, setShowActivity] = React.useState(false);
189
+ const [sidebarTab, setSidebarTab] = React.useState(() => {
190
+ if (typeof window === "undefined") return "overview";
191
+ const v = localStorage.getItem("support_sidebar_tab");
192
+ return v === "client" || v === "activity" ? v : "overview";
193
+ });
194
+ React.useEffect(() => {
195
+ if (typeof window === "undefined") return;
196
+ localStorage.setItem("support_sidebar_tab", sidebarTab);
197
+ }, [sidebarTab]);
198
+ const [previousTickets, setPreviousTickets] = React.useState([]);
156
199
  const [clientSummary, setClientSummary] = React.useState(null);
157
200
  const [summaryLoading, setSummaryLoading] = React.useState(false);
158
201
  const [timerRunning, setTimerRunning] = React.useState(() => {
@@ -229,6 +272,22 @@ const TicketDetailClient = () => {
229
272
  }).catch(() => {
230
273
  }).finally(() => setSummaryLoading(false));
231
274
  }, [ticket?.id]);
275
+ React.useEffect(() => {
276
+ if (sidebarTab !== "client" || !ticket || !ticketId) return;
277
+ const clientId = typeof ticket.client === "object" ? ticket.client?.id : ticket.client;
278
+ if (!clientId) return;
279
+ let aborted = false;
280
+ fetch(
281
+ `/api/tickets?where[client][equals]=${clientId}&where[id][not_equals]=${ticketId}&limit=5&sort=-createdAt&depth=0`,
282
+ { credentials: "include" }
283
+ ).then((r) => r.ok ? r.json() : null).then((d) => {
284
+ if (!aborted && d?.docs) setPreviousTickets(d.docs);
285
+ }).catch(() => {
286
+ });
287
+ return () => {
288
+ aborted = true;
289
+ };
290
+ }, [sidebarTab, ticket?.id, ticketId]);
232
291
  React.useEffect(() => {
233
292
  if (!ticketId || loading) return;
234
293
  const iv = setInterval(async () => {
@@ -432,6 +491,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
432
491
  ...finalHtml ? { bodyHtml: finalHtml } : {},
433
492
  authorType: sendAsClient ? "client" : "admin",
434
493
  ...sendAsClient && client ? { authorClient: client.id } : {},
494
+ ...fromAlias && !sendAsClient ? { fromAlias } : {},
435
495
  isInternal: sendAsClient ? false : isInternal,
436
496
  skipNotification: sendAsClient || isInternal || !notifyClient
437
497
  })
@@ -441,6 +501,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
441
501
  setReplyHtml("");
442
502
  setIsInternal(false);
443
503
  setSendAsClient(false);
504
+ setFromAlias("");
444
505
  setPendingFiles([]);
445
506
  editorRef.current?.clear();
446
507
  fetchAll();
@@ -535,21 +596,29 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
535
596
  method: "POST",
536
597
  headers: { "Content-Type": "application/json" },
537
598
  credentials: "include",
538
- body: JSON.stringify({ action: "suggest_reply", messages: messages.slice(-10).map((m) => ({ authorType: m.authorType, body: m.body })), clientName: `${client?.firstName || ""} ${client?.lastName || ""}`.trim(), clientCompany: client?.company })
599
+ body: JSON.stringify({
600
+ action: "suggest_reply",
601
+ messages: messages.slice(-10).map((m) => ({ authorType: m.authorType, body: m.body })),
602
+ clientName: `${client?.firstName || ""} ${client?.lastName || ""}`.trim(),
603
+ clientCompany: client?.company
604
+ })
539
605
  });
540
606
  if (r.ok) {
541
607
  const d = await r.json();
542
- if (d.reply) {
543
- setReplyBody(d.reply);
544
- setReplyHtml(d.reply.replace(/\n/g, "<br/>"));
545
- editorRef.current?.setContent(d.reply.replace(/\n/g, "<br/>"));
546
- }
608
+ if (d.reply) setAiSuggestion(d.reply);
547
609
  }
548
610
  } catch {
549
611
  } finally {
550
612
  setAiReplying(false);
551
613
  }
552
614
  };
615
+ const handleAiSuggestionInsert = () => {
616
+ if (!aiSuggestion) return;
617
+ setReplyBody(aiSuggestion);
618
+ setReplyHtml(aiSuggestion.replace(/\n/g, "<br/>"));
619
+ editorRef.current?.setContent(aiSuggestion.replace(/\n/g, "<br/>"));
620
+ setAiSuggestion(null);
621
+ };
553
622
  const handleAiRewrite = async (style = "auto") => {
554
623
  if (!replyBody.trim()) return;
555
624
  setAiRewriting(true);
@@ -749,7 +818,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
749
818
  ),
750
819
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.messageContent, children: [
751
820
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.messageHeader, children: [
752
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.messageAuthor, children: isAdmin ? "Support" : msg.authorType === "email" ? "Email" : client?.firstName || "Client" }),
821
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.messageAuthor, children: msg.fromAlias || (isAdmin ? "Support" : msg.authorType === "email" ? "Email" : client?.firstName || "Client") }),
753
822
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.messageTime, children: timeAgo(msg.createdAt) }),
754
823
  msg.isInternal && /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.badge, style: { background: "#fef3c7", color: "#92400e" }, children: "Interne" }),
755
824
  msg.isSolution && /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.badge, style: { background: "#dcfce7", color: "#166534" }, children: "Solution" }),
@@ -835,6 +904,27 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
835
904
  ] }),
836
905
  t("detail.typing", { name: client?.firstName || "Client" })
837
906
  ] }),
907
+ aiSuggestion && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.aiSuggestionPreview, role: "region", "aria-label": t("composer.aiSuggestion.label"), children: [
908
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.aiSuggestionPreviewHeader, children: [
909
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.aiSuggestionIcon, "aria-hidden": true, children: "\u2728" }),
910
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.aiSuggestionLabel, children: t("composer.aiSuggestion.label") }),
911
+ /* @__PURE__ */ jsxRuntime.jsx(
912
+ "button",
913
+ {
914
+ type: "button",
915
+ className: s__default.default.aiSuggestionDismiss,
916
+ onClick: () => setAiSuggestion(null),
917
+ "aria-label": t("composer.aiSuggestion.ignore"),
918
+ children: "\xD7"
919
+ }
920
+ )
921
+ ] }),
922
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.aiSuggestionBody, children: aiSuggestion }),
923
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.aiSuggestionActions, children: [
924
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: s__default.default.aiSuggestionIgnore, onClick: () => setAiSuggestion(null), children: t("composer.aiSuggestion.ignore") }),
925
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: s__default.default.aiSuggestionInsert, onClick: handleAiSuggestionInsert, children: t("composer.aiSuggestion.insert") })
926
+ ] })
927
+ ] }),
838
928
  /* @__PURE__ */ jsxRuntime.jsxs(
839
929
  "div",
840
930
  {
@@ -856,7 +946,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
856
946
  "aria-label": t("detail.iaSuggestion"),
857
947
  onClick: handleAiSuggest,
858
948
  disabled: aiReplying || messages.length === 0,
859
- children: aiReplying ? "\u2026" : `\u2728 ${t("detail.iaSuggestion")}`
949
+ children: aiReplying ? `\u23F3 ${t("composer.aiSuggestion.loading")}` : `\u2728 ${t("detail.iaSuggestion")}`
860
950
  }
861
951
  ),
862
952
  /* @__PURE__ */ jsxRuntime.jsx(RewriteDropdown, { disabled: aiRewriting || !replyBody.trim(), loading: aiRewriting, onSelect: (style) => handleAiRewrite(style), toolbarBtnClass: s__default.default.toolbarBtn })
@@ -968,6 +1058,20 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
968
1058
  ]
969
1059
  }
970
1060
  ),
1061
+ !sendAsClient && /* @__PURE__ */ jsxRuntime.jsxs(
1062
+ "select",
1063
+ {
1064
+ className: s__default.default.composerAliasSelect,
1065
+ value: fromAlias,
1066
+ onChange: (e) => setFromAlias(e.target.value),
1067
+ "aria-label": t("composer.aliases.label"),
1068
+ title: t("composer.aliases.tooltip"),
1069
+ children: [
1070
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t("composer.aliases.self") }),
1071
+ ALIASES.map((a) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: a.value, children: a.label }, a.value))
1072
+ ]
1073
+ }
1074
+ ),
971
1075
  !sendAsClient && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
972
1076
  /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
973
1077
  /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: isInternal, onChange: (e) => setIsInternal(e.target.checked) }),
@@ -992,294 +1096,396 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
992
1096
  )
993
1097
  ] }),
994
1098
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sidebar, children: [
995
- client && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
996
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientCard, children: [
997
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.clientAvatar, children: initials }),
998
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientInfo, children: [
999
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientName, children: [
1000
- client.firstName,
1001
- " ",
1002
- client.lastName
1003
- ] }),
1004
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.clientCompany, children: client.company }),
1005
- /* @__PURE__ */ jsxRuntime.jsx("a", { href: `mailto:${client.email}`, className: s__default.default.clientEmail, children: client.email })
1006
- ] })
1007
- ] }),
1008
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientActions, children: [
1009
- /* @__PURE__ */ jsxRuntime.jsx(Link__default.default, { href: `/admin/collections/support-clients/${client.id}`, className: s__default.default.smallBtn, children: t("client.clientSheet") }),
1010
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.smallBtn, onClick: () => window.open(`/api/admin/impersonate?clientId=${client.id}`, "_blank"), children: t("client.clientPortal") })
1011
- ] })
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}`)) })
1099
+ /* @__PURE__ */ jsxRuntime.jsxs("nav", { role: "tablist", "aria-label": t("detail.sidebar.tabs.overview"), className: s__default.default.sidebarTabs, children: [
1100
+ /* @__PURE__ */ jsxRuntime.jsx(
1101
+ "button",
1102
+ {
1103
+ type: "button",
1104
+ role: "tab",
1105
+ id: "sidebar-tab-overview",
1106
+ "aria-selected": sidebarTab === "overview",
1107
+ "aria-controls": "sidebar-panel-overview",
1108
+ tabIndex: sidebarTab === "overview" ? 0 : -1,
1109
+ className: `${s__default.default.sidebarTab} ${sidebarTab === "overview" ? s__default.default.sidebarTabActive : ""}`,
1110
+ onClick: () => setSidebarTab("overview"),
1111
+ children: t("detail.sidebar.tabs.overview")
1112
+ }
1113
+ ),
1114
+ /* @__PURE__ */ jsxRuntime.jsx(
1115
+ "button",
1116
+ {
1117
+ type: "button",
1118
+ role: "tab",
1119
+ id: "sidebar-tab-client",
1120
+ "aria-selected": sidebarTab === "client",
1121
+ "aria-controls": "sidebar-panel-client",
1122
+ tabIndex: sidebarTab === "client" ? 0 : -1,
1123
+ className: `${s__default.default.sidebarTab} ${sidebarTab === "client" ? s__default.default.sidebarTabActive : ""}`,
1124
+ onClick: () => setSidebarTab("client"),
1125
+ children: t("detail.sidebar.tabs.client")
1126
+ }
1127
+ ),
1128
+ /* @__PURE__ */ jsxRuntime.jsx(
1129
+ "button",
1130
+ {
1131
+ type: "button",
1132
+ role: "tab",
1133
+ id: "sidebar-tab-activity",
1134
+ "aria-selected": sidebarTab === "activity",
1135
+ "aria-controls": "sidebar-panel-activity",
1136
+ tabIndex: sidebarTab === "activity" ? 0 : -1,
1137
+ className: `${s__default.default.sidebarTab} ${sidebarTab === "activity" ? s__default.default.sidebarTabActive : ""}`,
1138
+ onClick: () => setSidebarTab("activity"),
1139
+ children: t("detail.sidebar.tabs.activity")
1140
+ }
1141
+ )
1024
1142
  ] }),
1025
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1026
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: t("detail.details") }),
1027
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
1028
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideLabel, children: t("detail.priority") }),
1029
- /* @__PURE__ */ jsxRuntime.jsxs("select", { className: s__default.default.sideSelect, value: ticket.priority || "normal", onChange: (e) => handleFieldPatch("priority", e.target.value), "aria-label": t("detail.priority"), children: [
1030
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "low", children: t("ticket.priority.low") }),
1031
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "normal", children: t("ticket.priority.normal") }),
1032
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "high", children: t("ticket.priority.high") }),
1033
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "urgent", children: t("ticket.priority.urgent") })
1143
+ sidebarTab === "overview" && /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "tabpanel", id: "sidebar-panel-overview", "aria-labelledby": "sidebar-tab-overview", children: [
1144
+ client && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1145
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientCard, children: [
1146
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.clientAvatar, children: initials }),
1147
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientInfo, children: [
1148
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientName, children: [
1149
+ client.firstName,
1150
+ " ",
1151
+ client.lastName
1152
+ ] }),
1153
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.clientCompany, children: client.company }),
1154
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: `mailto:${client.email}`, className: s__default.default.clientEmail, children: client.email })
1155
+ ] })
1156
+ ] }),
1157
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientActions, children: [
1158
+ /* @__PURE__ */ jsxRuntime.jsx(Link__default.default, { href: `/admin/collections/support-clients/${client.id}`, className: s__default.default.smallBtn, children: t("client.clientSheet") }),
1159
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.smallBtn, onClick: () => window.open(`/api/admin/impersonate?clientId=${client.id}`, "_blank"), children: t("client.clientPortal") })
1034
1160
  ] })
1035
1161
  ] }),
1036
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
1037
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideLabel, children: t("detail.category") }),
1038
- /* @__PURE__ */ jsxRuntime.jsxs("select", { className: s__default.default.sideSelect, value: ticket.category || "", onChange: (e) => handleFieldPatch("category", e.target.value), "aria-label": t("detail.category"), children: [
1039
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "\u2014" }),
1040
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "bug", children: t("ticket.category.bug") }),
1041
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "content", children: t("ticket.category.content") }),
1042
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "feature", children: t("ticket.category.feature") }),
1043
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "question", children: t("ticket.category.question") }),
1044
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "hosting", children: t("ticket.category.hosting") })
1162
+ clientSummary && clientSummary.summary && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${s__default.default.sideSection} ${s__default.default.aiCard}`, children: [
1163
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.aiCardTitle, children: [
1164
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.aiCardIcon, "aria-hidden": true, children: "\u2728" }),
1165
+ "Synth\xE8se client"
1166
+ ] }),
1167
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: s__default.default.aiCardLead, children: clientSummary.summary }),
1168
+ 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}`)) }),
1169
+ 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: [
1170
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.aiCardDot, "aria-hidden": true }),
1171
+ f
1172
+ ] }, `kf-${i}`)) }),
1173
+ clientSummary?.nextActions && clientSummary.nextActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1174
+ /* @__PURE__ */ jsxRuntime.jsx("h5", { className: s__default.default.aiCardNextTitle, children: t("detail.aiCard.nextActions.title") }),
1175
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: s__default.default.aiCardNextActions, children: clientSummary.nextActions.map((action, i) => /* @__PURE__ */ jsxRuntime.jsx(
1176
+ NextActionItem,
1177
+ {
1178
+ action,
1179
+ index: i,
1180
+ onToggle: async (done) => {
1181
+ const updatedActions = (clientSummary.nextActions || []).map(
1182
+ (a, j) => j === i ? { ...a, done } : a
1183
+ );
1184
+ if (clientSummary.id != null) {
1185
+ try {
1186
+ await fetch(`/api/client-summaries/${clientSummary.id}`, {
1187
+ method: "PATCH",
1188
+ credentials: "include",
1189
+ headers: { "Content-Type": "application/json" },
1190
+ body: JSON.stringify({ nextActions: updatedActions })
1191
+ });
1192
+ } catch {
1193
+ }
1194
+ }
1195
+ setClientSummary({ ...clientSummary, nextActions: updatedActions });
1196
+ }
1197
+ },
1198
+ action.id || `na-${i}`
1199
+ )) })
1045
1200
  ] })
1046
1201
  ] }),
1047
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
1048
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideLabel, children: t("detail.source") }),
1049
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideValue, children: ticket.source || t("ticket.source.portal") })
1202
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1203
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: t("detail.details") }),
1204
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
1205
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideLabel, children: t("detail.priority") }),
1206
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { className: s__default.default.sideSelect, value: ticket.priority || "normal", onChange: (e) => handleFieldPatch("priority", e.target.value), "aria-label": t("detail.priority"), children: [
1207
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "low", children: t("ticket.priority.low") }),
1208
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "normal", children: t("ticket.priority.normal") }),
1209
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "high", children: t("ticket.priority.high") }),
1210
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "urgent", children: t("ticket.priority.urgent") })
1211
+ ] })
1212
+ ] }),
1213
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
1214
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideLabel, children: t("detail.category") }),
1215
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { className: s__default.default.sideSelect, value: ticket.category || "", onChange: (e) => handleFieldPatch("category", e.target.value), "aria-label": t("detail.category"), children: [
1216
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "\u2014" }),
1217
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "bug", children: t("ticket.category.bug") }),
1218
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "content", children: t("ticket.category.content") }),
1219
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "feature", children: t("ticket.category.feature") }),
1220
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "question", children: t("ticket.category.question") }),
1221
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "hosting", children: t("ticket.category.hosting") })
1222
+ ] })
1223
+ ] }),
1224
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
1225
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideLabel, children: t("detail.source") }),
1226
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideValue, children: ticket.source || t("ticket.source.portal") })
1227
+ ] }),
1228
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
1229
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideLabel, children: t("detail.assigned") }),
1230
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideValue, children: typeof ticket.assignedTo === "object" && ticket.assignedTo ? ticket.assignedTo.firstName || "Admin" : "\u2014" })
1231
+ ] })
1050
1232
  ] }),
1051
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideField, children: [
1052
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideLabel, children: t("detail.assigned") }),
1053
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.sideValue, children: typeof ticket.assignedTo === "object" && ticket.assignedTo ? ticket.assignedTo.firstName || "Admin" : "\u2014" })
1054
- ] })
1055
- ] }),
1056
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1057
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: t("detail.tags") }),
1058
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.tagsWrap, children: [
1059
- tags.map((tag) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: s__default.default.tagChip, children: [
1060
- tag,
1061
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.tagRemove, "aria-label": `Retirer le tag ${tag}`, onClick: () => handleRemoveTag(tag), children: "\xD7" })
1062
- ] }, tag)),
1063
- addingTag ? /* @__PURE__ */ jsxRuntime.jsx(
1064
- "input",
1065
- {
1066
- className: s__default.default.tagInput,
1067
- value: newTagValue,
1068
- onChange: (e) => setNewTagValue(e.target.value),
1069
- onKeyDown: (e) => {
1070
- if (e.key === "Enter") handleAddTag();
1071
- if (e.key === "Escape") {
1072
- setAddingTag(false);
1073
- setNewTagValue("");
1233
+ features.timeTracking && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1234
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSectionTitle, children: [
1235
+ t("detail.time"),
1236
+ " ",
1237
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 700, fontSize: 13, color: "#d97706" }, children: totalMin > 0 ? `${Math.floor(totalMin / 60)}h${String(totalMin % 60).padStart(2, "0")} ${t("detail.total")}` : "0min" })
1238
+ ] }),
1239
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.timer, children: [
1240
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `${s__default.default.timerDisplay} ${timerRunning ? s__default.default.timerActive : ""}`, children: [
1241
+ String(Math.floor(timerSeconds / 60)).padStart(2, "0"),
1242
+ ":",
1243
+ String(timerSeconds % 60).padStart(2, "0")
1244
+ ] }),
1245
+ !timerRunning ? /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.timerBtn, onClick: () => setTimerRunning(true), style: { color: "#dc2626", borderColor: "#dc2626" }, "aria-label": "D\xE9marrer le timer", children: timerSeconds > 0 ? "\u25B6" : "\u25B6 Go" }) : /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.timerBtn, onClick: () => setTimerRunning(false), "aria-label": "Mettre en pause le timer", children: "\u23F8" }),
1246
+ timerSeconds >= 60 && !timerRunning && /* @__PURE__ */ jsxRuntime.jsxs("button", { className: s__default.default.timerBtn, onClick: () => {
1247
+ handleTimerSave();
1248
+ localStorage.removeItem(`timer-sec-${ticketId}`);
1249
+ localStorage.removeItem(`timer-run-${ticketId}`);
1250
+ }, style: { color: "#16a34a", borderColor: "#16a34a" }, "aria-label": "Sauvegarder le temps", children: [
1251
+ "\u{1F4BE} ",
1252
+ Math.round(timerSeconds / 60),
1253
+ "m"
1254
+ ] })
1255
+ ] }),
1256
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, marginTop: 8, alignItems: "center" }, children: [
1257
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "number", min: "1", placeholder: "min", style: { width: 60, padding: "4px 8px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 12, color: "var(--theme-text)", background: "var(--theme-elevation-0)" }, id: "manual-time-input" }),
1258
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.timerBtn, onClick: async () => {
1259
+ const input = document.getElementById("manual-time-input");
1260
+ const mins = Number(input?.value);
1261
+ if (!mins || mins < 1 || !ticketId) return;
1262
+ try {
1263
+ await fetch("/api/time-entries", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ ticket: Number(ticketId), duration: mins, date: (/* @__PURE__ */ new Date()).toISOString(), description: "Saisie manuelle" }) });
1264
+ if (input) input.value = "";
1265
+ fetchAll();
1266
+ } catch {
1267
+ }
1268
+ }, style: { fontSize: 11 }, children: "+ Ajouter" })
1269
+ ] }),
1270
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "var(--theme-elevation-500)" }, children: [
1271
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "2px 0", alignItems: "center" }, children: [
1272
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Facturable" }),
1273
+ /* @__PURE__ */ jsxRuntime.jsx(
1274
+ "button",
1275
+ {
1276
+ onClick: async () => {
1277
+ const newVal = ticket.billable === false ? true : false;
1278
+ try {
1279
+ await fetch(`/api/tickets/${ticketId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ billable: newVal }) });
1280
+ fetchAll();
1281
+ } catch {
1282
+ }
1283
+ },
1284
+ style: { fontWeight: 600, color: ticket.billable !== false ? "#16a34a" : "#dc2626", background: "none", border: "none", cursor: "pointer", fontSize: 11, textDecoration: "underline" },
1285
+ children: ticket.billable !== false ? "Oui" : "Non"
1074
1286
  }
1075
- },
1076
- onBlur: handleAddTag,
1077
- placeholder: "Tag...",
1078
- autoFocus: true
1079
- }
1080
- ) : /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.tagAddBtn, onClick: () => setAddingTag(true), "aria-label": "Ajouter un tag", children: "+ Tag" })
1081
- ] })
1082
- ] }),
1083
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1084
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: "Facturation" }),
1085
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
1086
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1087
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12 }, children: "Type" }),
1088
- /* @__PURE__ */ jsxRuntime.jsxs(
1089
- "select",
1287
+ )
1288
+ ] }),
1289
+ totalMin > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "2px 0" }, children: [
1290
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Montant estim\xE9" }),
1291
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 700, color: "var(--theme-text)" }, children: [
1292
+ (totalMin / 60 * 60).toFixed(0),
1293
+ "\u20AC"
1294
+ ] })
1295
+ ] })
1296
+ ] }),
1297
+ timeEntries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 8, fontSize: 11 }, children: timeEntries.slice(0, 6).map((e) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "3px 0", color: "var(--theme-elevation-500)" }, children: [
1298
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: new Date(e.date).toLocaleDateString("fr-FR", { day: "numeric", month: "short" }) }),
1299
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { title: e.description, style: { fontWeight: 600, cursor: e.description ? "help" : "default" }, children: [
1300
+ e.duration,
1301
+ "min"
1302
+ ] })
1303
+ ] }, e.id)) })
1304
+ ] }),
1305
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1306
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: t("detail.tags") }),
1307
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.tagsWrap, children: [
1308
+ tags.map((tag) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: s__default.default.tagChip, children: [
1309
+ tag,
1310
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.tagRemove, "aria-label": `Retirer le tag ${tag}`, onClick: () => handleRemoveTag(tag), children: "\xD7" })
1311
+ ] }, tag)),
1312
+ addingTag ? /* @__PURE__ */ jsxRuntime.jsx(
1313
+ "input",
1090
1314
  {
1091
- value: ticket?.billingType || "hourly",
1092
- onChange: async (e) => {
1093
- try {
1094
- await fetch(`/api/tickets/${ticketId}`, {
1095
- method: "PATCH",
1096
- credentials: "include",
1097
- headers: { "Content-Type": "application/json" },
1098
- body: JSON.stringify({ billingType: e.target.value })
1099
- });
1100
- fetchAll();
1101
- } catch {
1315
+ className: s__default.default.tagInput,
1316
+ value: newTagValue,
1317
+ onChange: (e) => setNewTagValue(e.target.value),
1318
+ onKeyDown: (e) => {
1319
+ if (e.key === "Enter") handleAddTag();
1320
+ if (e.key === "Escape") {
1321
+ setAddingTag(false);
1322
+ setNewTagValue("");
1102
1323
  }
1103
1324
  },
1104
- style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", background: "#fff" },
1105
- children: [
1106
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "hourly", children: "Au temps" }),
1107
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "flat", children: "Forfait" })
1108
- ]
1325
+ onBlur: handleAddTag,
1326
+ placeholder: "Tag...",
1327
+ autoFocus: true
1109
1328
  }
1110
- )
1111
- ] }),
1112
- ticket?.billingType === "flat" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1113
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12 }, children: "Montant forfait" }),
1114
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1115
- /* @__PURE__ */ jsxRuntime.jsx(
1116
- "input",
1329
+ ) : /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.tagAddBtn, onClick: () => setAddingTag(true), "aria-label": "Ajouter un tag", children: "+ Tag" })
1330
+ ] })
1331
+ ] }),
1332
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1333
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: "Facturation" }),
1334
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
1335
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1336
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12 }, children: "Type" }),
1337
+ /* @__PURE__ */ jsxRuntime.jsxs(
1338
+ "select",
1117
1339
  {
1118
- type: "number",
1119
- defaultValue: ticket?.flatRateAmount ?? "",
1120
- placeholder: "0",
1121
- onBlur: async (e) => {
1122
- const val = e.target.value ? Number(e.target.value) : null;
1340
+ value: ticket?.billingType || "hourly",
1341
+ onChange: async (e) => {
1123
1342
  try {
1124
1343
  await fetch(`/api/tickets/${ticketId}`, {
1125
1344
  method: "PATCH",
1126
1345
  credentials: "include",
1127
1346
  headers: { "Content-Type": "application/json" },
1128
- body: JSON.stringify({ flatRateAmount: val })
1347
+ body: JSON.stringify({ billingType: e.target.value })
1129
1348
  });
1130
1349
  fetchAll();
1131
1350
  } catch {
1132
1351
  }
1133
1352
  },
1134
- style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", width: 80, textAlign: "right" }
1353
+ style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", background: "#fff" },
1354
+ children: [
1355
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "hourly", children: "Au temps" }),
1356
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "flat", children: "Forfait" })
1357
+ ]
1135
1358
  }
1136
- ),
1137
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#9ca3af" }, children: "\u20AC" })
1138
- ] })
1139
- ] }),
1140
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1141
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12 }, children: "Montant factur\xE9" }),
1142
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1143
- /* @__PURE__ */ jsxRuntime.jsx(
1144
- "input",
1359
+ )
1360
+ ] }),
1361
+ ticket?.billingType === "flat" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1362
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12 }, children: "Montant forfait" }),
1363
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1364
+ /* @__PURE__ */ jsxRuntime.jsx(
1365
+ "input",
1366
+ {
1367
+ type: "number",
1368
+ defaultValue: ticket?.flatRateAmount ?? "",
1369
+ placeholder: "0",
1370
+ onBlur: async (e) => {
1371
+ const val = e.target.value ? Number(e.target.value) : null;
1372
+ try {
1373
+ await fetch(`/api/tickets/${ticketId}`, {
1374
+ method: "PATCH",
1375
+ credentials: "include",
1376
+ headers: { "Content-Type": "application/json" },
1377
+ body: JSON.stringify({ flatRateAmount: val })
1378
+ });
1379
+ fetchAll();
1380
+ } catch {
1381
+ }
1382
+ },
1383
+ style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", width: 80, textAlign: "right" }
1384
+ }
1385
+ ),
1386
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#9ca3af" }, children: "\u20AC" })
1387
+ ] })
1388
+ ] }),
1389
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1390
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12 }, children: "Montant factur\xE9" }),
1391
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1392
+ /* @__PURE__ */ jsxRuntime.jsx(
1393
+ "input",
1394
+ {
1395
+ type: "number",
1396
+ defaultValue: ticket?.billedAmount ?? "",
1397
+ placeholder: "0",
1398
+ onBlur: async (e) => {
1399
+ const val = e.target.value ? Number(e.target.value) : null;
1400
+ try {
1401
+ await fetch(`/api/tickets/${ticketId}`, {
1402
+ method: "PATCH",
1403
+ credentials: "include",
1404
+ headers: { "Content-Type": "application/json" },
1405
+ body: JSON.stringify({ billedAmount: val })
1406
+ });
1407
+ fetchAll();
1408
+ } catch {
1409
+ }
1410
+ },
1411
+ style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", width: 80, textAlign: "right" }
1412
+ }
1413
+ ),
1414
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#9ca3af" }, children: "\u20AC" })
1415
+ ] })
1416
+ ] }),
1417
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1418
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12 }, children: "Paiement" }),
1419
+ /* @__PURE__ */ jsxRuntime.jsxs(
1420
+ "select",
1145
1421
  {
1146
- type: "number",
1147
- defaultValue: ticket?.billedAmount ?? "",
1148
- placeholder: "0",
1149
- onBlur: async (e) => {
1150
- const val = e.target.value ? Number(e.target.value) : null;
1422
+ value: ticket?.paymentStatus || "unpaid",
1423
+ onChange: async (e) => {
1151
1424
  try {
1152
1425
  await fetch(`/api/tickets/${ticketId}`, {
1153
1426
  method: "PATCH",
1154
1427
  credentials: "include",
1155
1428
  headers: { "Content-Type": "application/json" },
1156
- body: JSON.stringify({ billedAmount: val })
1429
+ body: JSON.stringify({ paymentStatus: e.target.value })
1157
1430
  });
1158
1431
  fetchAll();
1159
1432
  } catch {
1160
1433
  }
1161
1434
  },
1162
- style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", width: 80, textAlign: "right" }
1435
+ style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", background: "#fff" },
1436
+ children: [
1437
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "unpaid", children: "Non pay\xE9" }),
1438
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "partial", children: "Partiel" }),
1439
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "paid", children: "Pay\xE9" })
1440
+ ]
1163
1441
  }
1164
- ),
1165
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#9ca3af" }, children: "\u20AC" })
1442
+ )
1166
1443
  ] })
1167
- ] }),
1168
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1169
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12 }, children: "Paiement" }),
1170
- /* @__PURE__ */ jsxRuntime.jsxs(
1171
- "select",
1172
- {
1173
- value: ticket?.paymentStatus || "unpaid",
1174
- onChange: async (e) => {
1175
- try {
1176
- await fetch(`/api/tickets/${ticketId}`, {
1177
- method: "PATCH",
1178
- credentials: "include",
1179
- headers: { "Content-Type": "application/json" },
1180
- body: JSON.stringify({ paymentStatus: e.target.value })
1181
- });
1182
- fetchAll();
1183
- } catch {
1184
- }
1185
- },
1186
- style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", background: "#fff" },
1187
- children: [
1188
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "unpaid", children: "Non pay\xE9" }),
1189
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "partial", children: "Partiel" }),
1190
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "paid", children: "Pay\xE9" })
1191
- ]
1192
- }
1193
- )
1194
1444
  ] })
1195
- ] })
1196
- ] }),
1197
- features.timeTracking && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1198
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSectionTitle, children: [
1199
- t("detail.time"),
1200
- " ",
1201
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 700, fontSize: 13, color: "#d97706" }, children: totalMin > 0 ? `${Math.floor(totalMin / 60)}h${String(totalMin % 60).padStart(2, "0")} ${t("detail.total")}` : "0min" })
1202
1445
  ] }),
1203
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.timer, children: [
1204
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `${s__default.default.timerDisplay} ${timerRunning ? s__default.default.timerActive : ""}`, children: [
1205
- String(Math.floor(timerSeconds / 60)).padStart(2, "0"),
1206
- ":",
1207
- String(timerSeconds % 60).padStart(2, "0")
1446
+ 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" }) })
1447
+ ] }),
1448
+ sidebarTab === "client" && /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "tabpanel", id: "sidebar-panel-client", "aria-labelledby": "sidebar-tab-client", children: [
1449
+ client && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1450
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientCard, children: [
1451
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.clientAvatar, children: initials }),
1452
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientInfo, children: [
1453
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientName, children: [
1454
+ client.firstName,
1455
+ " ",
1456
+ client.lastName
1457
+ ] }),
1458
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.clientCompany, children: client.company }),
1459
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: `mailto:${client.email}`, className: s__default.default.clientEmail, children: client.email }),
1460
+ client.phone && /* @__PURE__ */ jsxRuntime.jsx("a", { href: `tel:${client.phone}`, className: s__default.default.clientEmail, children: client.phone })
1461
+ ] })
1208
1462
  ] }),
1209
- !timerRunning ? /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.timerBtn, onClick: () => setTimerRunning(true), style: { color: "#dc2626", borderColor: "#dc2626" }, "aria-label": "D\xE9marrer le timer", children: timerSeconds > 0 ? "\u25B6" : "\u25B6 Go" }) : /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.timerBtn, onClick: () => setTimerRunning(false), "aria-label": "Mettre en pause le timer", children: "\u23F8" }),
1210
- timerSeconds >= 60 && !timerRunning && /* @__PURE__ */ jsxRuntime.jsxs("button", { className: s__default.default.timerBtn, onClick: () => {
1211
- handleTimerSave();
1212
- localStorage.removeItem(`timer-sec-${ticketId}`);
1213
- localStorage.removeItem(`timer-run-${ticketId}`);
1214
- }, style: { color: "#16a34a", borderColor: "#16a34a" }, "aria-label": "Sauvegarder le temps", children: [
1215
- "\u{1F4BE} ",
1216
- Math.round(timerSeconds / 60),
1217
- "m"
1463
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.clientActions, children: [
1464
+ /* @__PURE__ */ jsxRuntime.jsx(Link__default.default, { href: `/admin/collections/support-clients/${client.id}`, className: s__default.default.smallBtn, children: t("client.clientSheet") }),
1465
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.smallBtn, onClick: () => window.open(`/api/admin/impersonate?clientId=${client.id}`, "_blank"), children: t("client.clientPortal") })
1218
1466
  ] })
1219
1467
  ] }),
1220
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, marginTop: 8, alignItems: "center" }, children: [
1221
- /* @__PURE__ */ jsxRuntime.jsx("input", { type: "number", min: "1", placeholder: "min", style: { width: 60, padding: "4px 8px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 12, color: "var(--theme-text)", background: "var(--theme-elevation-0)" }, id: "manual-time-input" }),
1222
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: s__default.default.timerBtn, onClick: async () => {
1223
- const input = document.getElementById("manual-time-input");
1224
- const mins = Number(input?.value);
1225
- if (!mins || mins < 1 || !ticketId) return;
1226
- try {
1227
- await fetch("/api/time-entries", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ ticket: Number(ticketId), duration: mins, date: (/* @__PURE__ */ new Date()).toISOString(), description: "Saisie manuelle" }) });
1228
- if (input) input.value = "";
1229
- fetchAll();
1230
- } catch {
1231
- }
1232
- }, style: { fontSize: 11 }, children: "+ Ajouter" })
1233
- ] }),
1234
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "var(--theme-elevation-500)" }, children: [
1235
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "2px 0", alignItems: "center" }, children: [
1236
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Facturable" }),
1237
- /* @__PURE__ */ jsxRuntime.jsx(
1238
- "button",
1239
- {
1240
- onClick: async () => {
1241
- const newVal = ticket.billable === false ? true : false;
1242
- try {
1243
- await fetch(`/api/tickets/${ticketId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ billable: newVal }) });
1244
- fetchAll();
1245
- } catch {
1246
- }
1247
- },
1248
- style: { fontWeight: 600, color: ticket.billable !== false ? "#16a34a" : "#dc2626", background: "none", border: "none", cursor: "pointer", fontSize: 11, textDecoration: "underline" },
1249
- children: ticket.billable !== false ? "Oui" : "Non"
1250
- }
1251
- )
1252
- ] }),
1253
- totalMin > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "2px 0" }, children: [
1254
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Montant estim\xE9" }),
1255
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 700, color: "var(--theme-text)" }, children: [
1256
- (totalMin / 60 * 60).toFixed(0),
1257
- "\u20AC"
1468
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1469
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: t("detail.sidebar.previousTickets") }),
1470
+ previousTickets.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.previousTicketsEmpty, children: "\u2014" }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className: s__default.default.previousTicketsList, children: previousTickets.map((pt) => /* @__PURE__ */ jsxRuntime.jsx("li", { className: s__default.default.previousTicketsItem, children: /* @__PURE__ */ jsxRuntime.jsxs(Link__default.default, { href: `/admin/collections/tickets/${pt.id}`, className: s__default.default.previousTicketsLink, children: [
1471
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.previousTicketsSubject, children: pt.subject || `#${pt.id}` }),
1472
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: s__default.default.previousTicketsMeta, children: [
1473
+ pt.status ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.previousTicketsStatus, children: pt.status }) : null,
1474
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: s__default.default.previousTicketsDate, children: new Date(pt.createdAt).toLocaleDateString("fr-FR", { day: "numeric", month: "short", year: "numeric" }) })
1258
1475
  ] })
1259
- ] })
1260
- ] }),
1261
- timeEntries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 8, fontSize: 11 }, children: timeEntries.slice(0, 6).map((e) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "3px 0", color: "var(--theme-elevation-500)" }, children: [
1262
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: new Date(e.date).toLocaleDateString("fr-FR", { day: "numeric", month: "short" }) }),
1263
- /* @__PURE__ */ jsxRuntime.jsxs("span", { title: e.description, style: { fontWeight: 600, cursor: e.description ? "help" : "default" }, children: [
1264
- e.duration,
1265
- "min"
1266
- ] })
1267
- ] }, e.id)) })
1476
+ ] }) }, pt.id)) })
1477
+ ] })
1268
1478
  ] }),
1269
- features.activityLog && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1270
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: /* @__PURE__ */ jsxRuntime.jsxs("button", { className: s__default.default.collapseBtn, onClick: () => setShowActivity(!showActivity), "aria-label": showActivity ? "Masquer le journal" : "Afficher le journal", children: [
1271
- "Activit\xE9 ",
1272
- showActivity ? "\u25BE" : "\u25B8"
1273
- ] }) }),
1274
- showActivity && activityLog.slice(0, 8).map((a) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.activityItem, children: [
1479
+ sidebarTab === "activity" && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "tabpanel", id: "sidebar-panel-activity", "aria-labelledby": "sidebar-tab-activity", children: features.activityLog ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.sideSection, children: [
1480
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSectionTitle, children: t("detail.activity") }),
1481
+ activityLog.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.previousTicketsEmpty, children: "\u2014" }) : activityLog.slice(0, 30).map((a) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.activityItem, children: [
1275
1482
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.activityDot, style: { backgroundColor: a.actorType === "admin" ? "#2563eb" : a.actorType === "system" ? "#6b7280" : "#16a34a" } }),
1276
1483
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.activityContent, children: [
1277
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.activityText, children: (a.detail || a.action).slice(0, 60) }),
1484
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.activityText, children: (a.detail || a.action).slice(0, 120) }),
1278
1485
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.activityTime, children: timeAgo(a.createdAt) })
1279
1486
  ] })
1280
1487
  ] }, a.id))
1281
- ] }),
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" }) })
1488
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.sideSection, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: s__default.default.previousTicketsEmpty, children: "\u2014" }) }) })
1283
1489
  ] })
1284
1490
  ] }),
1285
1491
  undoToast && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: s__default.default.undoToast, role: "alert", children: [