@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
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import React, { useRef, useState, useCallback, useEffect } from 'react';
3
+ import React, { useRef, useState, useEffect, useCallback } from 'react';
4
4
  import { useSearchParams } from 'next/navigation';
5
5
  import Link from 'next/link';
6
6
  import { RichTextEditor } from '../../components/RichTextEditor/index.js';
@@ -11,6 +11,10 @@ import { computeSlaState, formatSlaRemaining } from '../shared/sla.js';
11
11
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
12
12
  import s from '../../styles/TicketDetail.module.scss';
13
13
 
14
+ const ALIASES = [
15
+ { value: "Support Consilioweb", label: "En tant que : Support Consilioweb" },
16
+ { value: "contact@consilioweb.fr", label: "En tant que : contact@consilioweb.fr" }
17
+ ];
14
18
  const STATUS_STYLE = {
15
19
  open: { bg: "#dbeafe", color: "#1e40af" },
16
20
  waiting_client: { bg: "#fef3c7", color: "#92400e" },
@@ -112,6 +116,34 @@ const RewriteDropdown = ({ disabled, loading, onSelect, toolbarBtnClass }) => {
112
116
  )) })
113
117
  ] });
114
118
  };
119
+ function NextActionItem({ action, index, onToggle }) {
120
+ const [pending, setPending] = useState(false);
121
+ const labelMap = { now: "Maintenant", today: "Aujourd'hui", "this-week": "Cette semaine" };
122
+ const done = !!action.done;
123
+ return /* @__PURE__ */ jsxs("li", { className: s.aiCardNextAction, children: [
124
+ /* @__PURE__ */ jsx(
125
+ "button",
126
+ {
127
+ type: "button",
128
+ className: `${s.aiCardNextCheck} ${done ? s.aiCardNextCheckDone : ""}`,
129
+ onClick: async () => {
130
+ setPending(true);
131
+ try {
132
+ await onToggle(!done);
133
+ } finally {
134
+ setPending(false);
135
+ }
136
+ },
137
+ "aria-pressed": done,
138
+ "aria-label": done ? "Marquer non fait" : "Marquer fait",
139
+ disabled: pending,
140
+ children: done && /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\u2713" })
141
+ }
142
+ ),
143
+ /* @__PURE__ */ jsx("span", { className: `${s.aiCardNextLabel} ${done ? s.aiCardNextLabelDone : ""}`, children: action.label }),
144
+ /* @__PURE__ */ jsx("span", { className: s.aiCardNextWhen, children: labelMap[action.priority] || action.priority })
145
+ ] });
146
+ }
115
147
  const TicketDetailClient = () => {
116
148
  const { t } = useTranslation();
117
149
  const searchParams = useSearchParams();
@@ -133,6 +165,7 @@ const TicketDetailClient = () => {
133
165
  const [isInternal, setIsInternal] = useState(false);
134
166
  const [notifyClient, setNotifyClient] = useState(true);
135
167
  const [sendAsClient, setSendAsClient] = useState(false);
168
+ const [fromAlias, setFromAlias] = useState("");
136
169
  const [editingMsgId, setEditingMsgId] = useState(null);
137
170
  const [editingBody, setEditingBody] = useState("");
138
171
  const [editingHtml, setEditingHtml] = useState("");
@@ -142,10 +175,20 @@ const TicketDetailClient = () => {
142
175
  const [showMenu, setShowMenu] = useState(false);
143
176
  const [clientTyping, setClientTyping] = useState(false);
144
177
  const [aiReplying, setAiReplying] = useState(false);
178
+ const [aiSuggestion, setAiSuggestion] = useState(null);
145
179
  const [aiRewriting, setAiRewriting] = useState(false);
146
180
  const [sentiment, setSentiment] = useState(null);
147
181
  const [statusUpdating, setStatusUpdating] = useState(false);
148
- const [showActivity, setShowActivity] = useState(false);
182
+ const [sidebarTab, setSidebarTab] = useState(() => {
183
+ if (typeof window === "undefined") return "overview";
184
+ const v = localStorage.getItem("support_sidebar_tab");
185
+ return v === "client" || v === "activity" ? v : "overview";
186
+ });
187
+ useEffect(() => {
188
+ if (typeof window === "undefined") return;
189
+ localStorage.setItem("support_sidebar_tab", sidebarTab);
190
+ }, [sidebarTab]);
191
+ const [previousTickets, setPreviousTickets] = useState([]);
149
192
  const [clientSummary, setClientSummary] = useState(null);
150
193
  const [summaryLoading, setSummaryLoading] = useState(false);
151
194
  const [timerRunning, setTimerRunning] = useState(() => {
@@ -222,6 +265,22 @@ const TicketDetailClient = () => {
222
265
  }).catch(() => {
223
266
  }).finally(() => setSummaryLoading(false));
224
267
  }, [ticket?.id]);
268
+ useEffect(() => {
269
+ if (sidebarTab !== "client" || !ticket || !ticketId) return;
270
+ const clientId = typeof ticket.client === "object" ? ticket.client?.id : ticket.client;
271
+ if (!clientId) return;
272
+ let aborted = false;
273
+ fetch(
274
+ `/api/tickets?where[client][equals]=${clientId}&where[id][not_equals]=${ticketId}&limit=5&sort=-createdAt&depth=0`,
275
+ { credentials: "include" }
276
+ ).then((r) => r.ok ? r.json() : null).then((d) => {
277
+ if (!aborted && d?.docs) setPreviousTickets(d.docs);
278
+ }).catch(() => {
279
+ });
280
+ return () => {
281
+ aborted = true;
282
+ };
283
+ }, [sidebarTab, ticket?.id, ticketId]);
225
284
  useEffect(() => {
226
285
  if (!ticketId || loading) return;
227
286
  const iv = setInterval(async () => {
@@ -425,6 +484,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
425
484
  ...finalHtml ? { bodyHtml: finalHtml } : {},
426
485
  authorType: sendAsClient ? "client" : "admin",
427
486
  ...sendAsClient && client ? { authorClient: client.id } : {},
487
+ ...fromAlias && !sendAsClient ? { fromAlias } : {},
428
488
  isInternal: sendAsClient ? false : isInternal,
429
489
  skipNotification: sendAsClient || isInternal || !notifyClient
430
490
  })
@@ -434,6 +494,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
434
494
  setReplyHtml("");
435
495
  setIsInternal(false);
436
496
  setSendAsClient(false);
497
+ setFromAlias("");
437
498
  setPendingFiles([]);
438
499
  editorRef.current?.clear();
439
500
  fetchAll();
@@ -528,21 +589,29 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
528
589
  method: "POST",
529
590
  headers: { "Content-Type": "application/json" },
530
591
  credentials: "include",
531
- 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 })
592
+ body: JSON.stringify({
593
+ action: "suggest_reply",
594
+ messages: messages.slice(-10).map((m) => ({ authorType: m.authorType, body: m.body })),
595
+ clientName: `${client?.firstName || ""} ${client?.lastName || ""}`.trim(),
596
+ clientCompany: client?.company
597
+ })
532
598
  });
533
599
  if (r.ok) {
534
600
  const d = await r.json();
535
- if (d.reply) {
536
- setReplyBody(d.reply);
537
- setReplyHtml(d.reply.replace(/\n/g, "<br/>"));
538
- editorRef.current?.setContent(d.reply.replace(/\n/g, "<br/>"));
539
- }
601
+ if (d.reply) setAiSuggestion(d.reply);
540
602
  }
541
603
  } catch {
542
604
  } finally {
543
605
  setAiReplying(false);
544
606
  }
545
607
  };
608
+ const handleAiSuggestionInsert = () => {
609
+ if (!aiSuggestion) return;
610
+ setReplyBody(aiSuggestion);
611
+ setReplyHtml(aiSuggestion.replace(/\n/g, "<br/>"));
612
+ editorRef.current?.setContent(aiSuggestion.replace(/\n/g, "<br/>"));
613
+ setAiSuggestion(null);
614
+ };
546
615
  const handleAiRewrite = async (style = "auto") => {
547
616
  if (!replyBody.trim()) return;
548
617
  setAiRewriting(true);
@@ -742,7 +811,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
742
811
  ),
743
812
  /* @__PURE__ */ jsxs("div", { className: s.messageContent, children: [
744
813
  /* @__PURE__ */ jsxs("div", { className: s.messageHeader, children: [
745
- /* @__PURE__ */ jsx("span", { className: s.messageAuthor, children: isAdmin ? "Support" : msg.authorType === "email" ? "Email" : client?.firstName || "Client" }),
814
+ /* @__PURE__ */ jsx("span", { className: s.messageAuthor, children: msg.fromAlias || (isAdmin ? "Support" : msg.authorType === "email" ? "Email" : client?.firstName || "Client") }),
746
815
  /* @__PURE__ */ jsx("span", { className: s.messageTime, children: timeAgo(msg.createdAt) }),
747
816
  msg.isInternal && /* @__PURE__ */ jsx("span", { className: s.badge, style: { background: "#fef3c7", color: "#92400e" }, children: "Interne" }),
748
817
  msg.isSolution && /* @__PURE__ */ jsx("span", { className: s.badge, style: { background: "#dcfce7", color: "#166534" }, children: "Solution" }),
@@ -828,6 +897,27 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
828
897
  ] }),
829
898
  t("detail.typing", { name: client?.firstName || "Client" })
830
899
  ] }),
900
+ aiSuggestion && /* @__PURE__ */ jsxs("div", { className: s.aiSuggestionPreview, role: "region", "aria-label": t("composer.aiSuggestion.label"), children: [
901
+ /* @__PURE__ */ jsxs("div", { className: s.aiSuggestionPreviewHeader, children: [
902
+ /* @__PURE__ */ jsx("span", { className: s.aiSuggestionIcon, "aria-hidden": true, children: "\u2728" }),
903
+ /* @__PURE__ */ jsx("span", { className: s.aiSuggestionLabel, children: t("composer.aiSuggestion.label") }),
904
+ /* @__PURE__ */ jsx(
905
+ "button",
906
+ {
907
+ type: "button",
908
+ className: s.aiSuggestionDismiss,
909
+ onClick: () => setAiSuggestion(null),
910
+ "aria-label": t("composer.aiSuggestion.ignore"),
911
+ children: "\xD7"
912
+ }
913
+ )
914
+ ] }),
915
+ /* @__PURE__ */ jsx("div", { className: s.aiSuggestionBody, children: aiSuggestion }),
916
+ /* @__PURE__ */ jsxs("div", { className: s.aiSuggestionActions, children: [
917
+ /* @__PURE__ */ jsx("button", { type: "button", className: s.aiSuggestionIgnore, onClick: () => setAiSuggestion(null), children: t("composer.aiSuggestion.ignore") }),
918
+ /* @__PURE__ */ jsx("button", { type: "button", className: s.aiSuggestionInsert, onClick: handleAiSuggestionInsert, children: t("composer.aiSuggestion.insert") })
919
+ ] })
920
+ ] }),
831
921
  /* @__PURE__ */ jsxs(
832
922
  "div",
833
923
  {
@@ -849,7 +939,7 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
849
939
  "aria-label": t("detail.iaSuggestion"),
850
940
  onClick: handleAiSuggest,
851
941
  disabled: aiReplying || messages.length === 0,
852
- children: aiReplying ? "\u2026" : `\u2728 ${t("detail.iaSuggestion")}`
942
+ children: aiReplying ? `\u23F3 ${t("composer.aiSuggestion.loading")}` : `\u2728 ${t("detail.iaSuggestion")}`
853
943
  }
854
944
  ),
855
945
  /* @__PURE__ */ jsx(RewriteDropdown, { disabled: aiRewriting || !replyBody.trim(), loading: aiRewriting, onSelect: (style) => handleAiRewrite(style), toolbarBtnClass: s.toolbarBtn })
@@ -961,6 +1051,20 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
961
1051
  ]
962
1052
  }
963
1053
  ),
1054
+ !sendAsClient && /* @__PURE__ */ jsxs(
1055
+ "select",
1056
+ {
1057
+ className: s.composerAliasSelect,
1058
+ value: fromAlias,
1059
+ onChange: (e) => setFromAlias(e.target.value),
1060
+ "aria-label": t("composer.aliases.label"),
1061
+ title: t("composer.aliases.tooltip"),
1062
+ children: [
1063
+ /* @__PURE__ */ jsx("option", { value: "", children: t("composer.aliases.self") }),
1064
+ ALIASES.map((a) => /* @__PURE__ */ jsx("option", { value: a.value, children: a.label }, a.value))
1065
+ ]
1066
+ }
1067
+ ),
964
1068
  !sendAsClient && /* @__PURE__ */ jsxs(Fragment, { children: [
965
1069
  /* @__PURE__ */ jsxs("label", { children: [
966
1070
  /* @__PURE__ */ jsx("input", { type: "checkbox", checked: isInternal, onChange: (e) => setIsInternal(e.target.checked) }),
@@ -985,294 +1089,396 @@ ${uploadedLinks.join("\n")}` : replyBody.trim() || "[Contenu enrichi]";
985
1089
  )
986
1090
  ] }),
987
1091
  /* @__PURE__ */ jsxs("div", { className: s.sidebar, children: [
988
- client && /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
989
- /* @__PURE__ */ jsxs("div", { className: s.clientCard, children: [
990
- /* @__PURE__ */ jsx("div", { className: s.clientAvatar, children: initials }),
991
- /* @__PURE__ */ jsxs("div", { className: s.clientInfo, children: [
992
- /* @__PURE__ */ jsxs("div", { className: s.clientName, children: [
993
- client.firstName,
994
- " ",
995
- client.lastName
996
- ] }),
997
- /* @__PURE__ */ jsx("div", { className: s.clientCompany, children: client.company }),
998
- /* @__PURE__ */ jsx("a", { href: `mailto:${client.email}`, className: s.clientEmail, children: client.email })
999
- ] })
1000
- ] }),
1001
- /* @__PURE__ */ jsxs("div", { className: s.clientActions, children: [
1002
- /* @__PURE__ */ jsx(Link, { href: `/admin/collections/support-clients/${client.id}`, className: s.smallBtn, children: t("client.clientSheet") }),
1003
- /* @__PURE__ */ jsx("button", { className: s.smallBtn, onClick: () => window.open(`/api/admin/impersonate?clientId=${client.id}`, "_blank"), children: t("client.clientPortal") })
1004
- ] })
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}`)) })
1092
+ /* @__PURE__ */ jsxs("nav", { role: "tablist", "aria-label": t("detail.sidebar.tabs.overview"), className: s.sidebarTabs, children: [
1093
+ /* @__PURE__ */ jsx(
1094
+ "button",
1095
+ {
1096
+ type: "button",
1097
+ role: "tab",
1098
+ id: "sidebar-tab-overview",
1099
+ "aria-selected": sidebarTab === "overview",
1100
+ "aria-controls": "sidebar-panel-overview",
1101
+ tabIndex: sidebarTab === "overview" ? 0 : -1,
1102
+ className: `${s.sidebarTab} ${sidebarTab === "overview" ? s.sidebarTabActive : ""}`,
1103
+ onClick: () => setSidebarTab("overview"),
1104
+ children: t("detail.sidebar.tabs.overview")
1105
+ }
1106
+ ),
1107
+ /* @__PURE__ */ jsx(
1108
+ "button",
1109
+ {
1110
+ type: "button",
1111
+ role: "tab",
1112
+ id: "sidebar-tab-client",
1113
+ "aria-selected": sidebarTab === "client",
1114
+ "aria-controls": "sidebar-panel-client",
1115
+ tabIndex: sidebarTab === "client" ? 0 : -1,
1116
+ className: `${s.sidebarTab} ${sidebarTab === "client" ? s.sidebarTabActive : ""}`,
1117
+ onClick: () => setSidebarTab("client"),
1118
+ children: t("detail.sidebar.tabs.client")
1119
+ }
1120
+ ),
1121
+ /* @__PURE__ */ jsx(
1122
+ "button",
1123
+ {
1124
+ type: "button",
1125
+ role: "tab",
1126
+ id: "sidebar-tab-activity",
1127
+ "aria-selected": sidebarTab === "activity",
1128
+ "aria-controls": "sidebar-panel-activity",
1129
+ tabIndex: sidebarTab === "activity" ? 0 : -1,
1130
+ className: `${s.sidebarTab} ${sidebarTab === "activity" ? s.sidebarTabActive : ""}`,
1131
+ onClick: () => setSidebarTab("activity"),
1132
+ children: t("detail.sidebar.tabs.activity")
1133
+ }
1134
+ )
1017
1135
  ] }),
1018
- /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1019
- /* @__PURE__ */ jsx("div", { className: s.sideSectionTitle, children: t("detail.details") }),
1020
- /* @__PURE__ */ jsxs("div", { className: s.sideField, children: [
1021
- /* @__PURE__ */ jsx("span", { className: s.sideLabel, children: t("detail.priority") }),
1022
- /* @__PURE__ */ jsxs("select", { className: s.sideSelect, value: ticket.priority || "normal", onChange: (e) => handleFieldPatch("priority", e.target.value), "aria-label": t("detail.priority"), children: [
1023
- /* @__PURE__ */ jsx("option", { value: "low", children: t("ticket.priority.low") }),
1024
- /* @__PURE__ */ jsx("option", { value: "normal", children: t("ticket.priority.normal") }),
1025
- /* @__PURE__ */ jsx("option", { value: "high", children: t("ticket.priority.high") }),
1026
- /* @__PURE__ */ jsx("option", { value: "urgent", children: t("ticket.priority.urgent") })
1136
+ sidebarTab === "overview" && /* @__PURE__ */ jsxs("div", { role: "tabpanel", id: "sidebar-panel-overview", "aria-labelledby": "sidebar-tab-overview", children: [
1137
+ client && /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1138
+ /* @__PURE__ */ jsxs("div", { className: s.clientCard, children: [
1139
+ /* @__PURE__ */ jsx("div", { className: s.clientAvatar, children: initials }),
1140
+ /* @__PURE__ */ jsxs("div", { className: s.clientInfo, children: [
1141
+ /* @__PURE__ */ jsxs("div", { className: s.clientName, children: [
1142
+ client.firstName,
1143
+ " ",
1144
+ client.lastName
1145
+ ] }),
1146
+ /* @__PURE__ */ jsx("div", { className: s.clientCompany, children: client.company }),
1147
+ /* @__PURE__ */ jsx("a", { href: `mailto:${client.email}`, className: s.clientEmail, children: client.email })
1148
+ ] })
1149
+ ] }),
1150
+ /* @__PURE__ */ jsxs("div", { className: s.clientActions, children: [
1151
+ /* @__PURE__ */ jsx(Link, { href: `/admin/collections/support-clients/${client.id}`, className: s.smallBtn, children: t("client.clientSheet") }),
1152
+ /* @__PURE__ */ jsx("button", { className: s.smallBtn, onClick: () => window.open(`/api/admin/impersonate?clientId=${client.id}`, "_blank"), children: t("client.clientPortal") })
1027
1153
  ] })
1028
1154
  ] }),
1029
- /* @__PURE__ */ jsxs("div", { className: s.sideField, children: [
1030
- /* @__PURE__ */ jsx("span", { className: s.sideLabel, children: t("detail.category") }),
1031
- /* @__PURE__ */ jsxs("select", { className: s.sideSelect, value: ticket.category || "", onChange: (e) => handleFieldPatch("category", e.target.value), "aria-label": t("detail.category"), children: [
1032
- /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
1033
- /* @__PURE__ */ jsx("option", { value: "bug", children: t("ticket.category.bug") }),
1034
- /* @__PURE__ */ jsx("option", { value: "content", children: t("ticket.category.content") }),
1035
- /* @__PURE__ */ jsx("option", { value: "feature", children: t("ticket.category.feature") }),
1036
- /* @__PURE__ */ jsx("option", { value: "question", children: t("ticket.category.question") }),
1037
- /* @__PURE__ */ jsx("option", { value: "hosting", children: t("ticket.category.hosting") })
1155
+ clientSummary && clientSummary.summary && /* @__PURE__ */ jsxs("div", { className: `${s.sideSection} ${s.aiCard}`, children: [
1156
+ /* @__PURE__ */ jsxs("div", { className: s.aiCardTitle, children: [
1157
+ /* @__PURE__ */ jsx("span", { className: s.aiCardIcon, "aria-hidden": true, children: "\u2728" }),
1158
+ "Synth\xE8se client"
1159
+ ] }),
1160
+ /* @__PURE__ */ jsx("p", { className: s.aiCardLead, children: clientSummary.summary }),
1161
+ 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}`)) }),
1162
+ 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: [
1163
+ /* @__PURE__ */ jsx("span", { className: s.aiCardDot, "aria-hidden": true }),
1164
+ f
1165
+ ] }, `kf-${i}`)) }),
1166
+ clientSummary?.nextActions && clientSummary.nextActions.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1167
+ /* @__PURE__ */ jsx("h5", { className: s.aiCardNextTitle, children: t("detail.aiCard.nextActions.title") }),
1168
+ /* @__PURE__ */ jsx("ul", { className: s.aiCardNextActions, children: clientSummary.nextActions.map((action, i) => /* @__PURE__ */ jsx(
1169
+ NextActionItem,
1170
+ {
1171
+ action,
1172
+ index: i,
1173
+ onToggle: async (done) => {
1174
+ const updatedActions = (clientSummary.nextActions || []).map(
1175
+ (a, j) => j === i ? { ...a, done } : a
1176
+ );
1177
+ if (clientSummary.id != null) {
1178
+ try {
1179
+ await fetch(`/api/client-summaries/${clientSummary.id}`, {
1180
+ method: "PATCH",
1181
+ credentials: "include",
1182
+ headers: { "Content-Type": "application/json" },
1183
+ body: JSON.stringify({ nextActions: updatedActions })
1184
+ });
1185
+ } catch {
1186
+ }
1187
+ }
1188
+ setClientSummary({ ...clientSummary, nextActions: updatedActions });
1189
+ }
1190
+ },
1191
+ action.id || `na-${i}`
1192
+ )) })
1038
1193
  ] })
1039
1194
  ] }),
1040
- /* @__PURE__ */ jsxs("div", { className: s.sideField, children: [
1041
- /* @__PURE__ */ jsx("span", { className: s.sideLabel, children: t("detail.source") }),
1042
- /* @__PURE__ */ jsx("span", { className: s.sideValue, children: ticket.source || t("ticket.source.portal") })
1195
+ /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1196
+ /* @__PURE__ */ jsx("div", { className: s.sideSectionTitle, children: t("detail.details") }),
1197
+ /* @__PURE__ */ jsxs("div", { className: s.sideField, children: [
1198
+ /* @__PURE__ */ jsx("span", { className: s.sideLabel, children: t("detail.priority") }),
1199
+ /* @__PURE__ */ jsxs("select", { className: s.sideSelect, value: ticket.priority || "normal", onChange: (e) => handleFieldPatch("priority", e.target.value), "aria-label": t("detail.priority"), children: [
1200
+ /* @__PURE__ */ jsx("option", { value: "low", children: t("ticket.priority.low") }),
1201
+ /* @__PURE__ */ jsx("option", { value: "normal", children: t("ticket.priority.normal") }),
1202
+ /* @__PURE__ */ jsx("option", { value: "high", children: t("ticket.priority.high") }),
1203
+ /* @__PURE__ */ jsx("option", { value: "urgent", children: t("ticket.priority.urgent") })
1204
+ ] })
1205
+ ] }),
1206
+ /* @__PURE__ */ jsxs("div", { className: s.sideField, children: [
1207
+ /* @__PURE__ */ jsx("span", { className: s.sideLabel, children: t("detail.category") }),
1208
+ /* @__PURE__ */ jsxs("select", { className: s.sideSelect, value: ticket.category || "", onChange: (e) => handleFieldPatch("category", e.target.value), "aria-label": t("detail.category"), children: [
1209
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
1210
+ /* @__PURE__ */ jsx("option", { value: "bug", children: t("ticket.category.bug") }),
1211
+ /* @__PURE__ */ jsx("option", { value: "content", children: t("ticket.category.content") }),
1212
+ /* @__PURE__ */ jsx("option", { value: "feature", children: t("ticket.category.feature") }),
1213
+ /* @__PURE__ */ jsx("option", { value: "question", children: t("ticket.category.question") }),
1214
+ /* @__PURE__ */ jsx("option", { value: "hosting", children: t("ticket.category.hosting") })
1215
+ ] })
1216
+ ] }),
1217
+ /* @__PURE__ */ jsxs("div", { className: s.sideField, children: [
1218
+ /* @__PURE__ */ jsx("span", { className: s.sideLabel, children: t("detail.source") }),
1219
+ /* @__PURE__ */ jsx("span", { className: s.sideValue, children: ticket.source || t("ticket.source.portal") })
1220
+ ] }),
1221
+ /* @__PURE__ */ jsxs("div", { className: s.sideField, children: [
1222
+ /* @__PURE__ */ jsx("span", { className: s.sideLabel, children: t("detail.assigned") }),
1223
+ /* @__PURE__ */ jsx("span", { className: s.sideValue, children: typeof ticket.assignedTo === "object" && ticket.assignedTo ? ticket.assignedTo.firstName || "Admin" : "\u2014" })
1224
+ ] })
1043
1225
  ] }),
1044
- /* @__PURE__ */ jsxs("div", { className: s.sideField, children: [
1045
- /* @__PURE__ */ jsx("span", { className: s.sideLabel, children: t("detail.assigned") }),
1046
- /* @__PURE__ */ jsx("span", { className: s.sideValue, children: typeof ticket.assignedTo === "object" && ticket.assignedTo ? ticket.assignedTo.firstName || "Admin" : "\u2014" })
1047
- ] })
1048
- ] }),
1049
- /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1050
- /* @__PURE__ */ jsx("div", { className: s.sideSectionTitle, children: t("detail.tags") }),
1051
- /* @__PURE__ */ jsxs("div", { className: s.tagsWrap, children: [
1052
- tags.map((tag) => /* @__PURE__ */ jsxs("span", { className: s.tagChip, children: [
1053
- tag,
1054
- /* @__PURE__ */ jsx("button", { className: s.tagRemove, "aria-label": `Retirer le tag ${tag}`, onClick: () => handleRemoveTag(tag), children: "\xD7" })
1055
- ] }, tag)),
1056
- addingTag ? /* @__PURE__ */ jsx(
1057
- "input",
1058
- {
1059
- className: s.tagInput,
1060
- value: newTagValue,
1061
- onChange: (e) => setNewTagValue(e.target.value),
1062
- onKeyDown: (e) => {
1063
- if (e.key === "Enter") handleAddTag();
1064
- if (e.key === "Escape") {
1065
- setAddingTag(false);
1066
- setNewTagValue("");
1226
+ features.timeTracking && /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1227
+ /* @__PURE__ */ jsxs("div", { className: s.sideSectionTitle, children: [
1228
+ t("detail.time"),
1229
+ " ",
1230
+ /* @__PURE__ */ 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" })
1231
+ ] }),
1232
+ /* @__PURE__ */ jsxs("div", { className: s.timer, children: [
1233
+ /* @__PURE__ */ jsxs("span", { className: `${s.timerDisplay} ${timerRunning ? s.timerActive : ""}`, children: [
1234
+ String(Math.floor(timerSeconds / 60)).padStart(2, "0"),
1235
+ ":",
1236
+ String(timerSeconds % 60).padStart(2, "0")
1237
+ ] }),
1238
+ !timerRunning ? /* @__PURE__ */ jsx("button", { className: s.timerBtn, onClick: () => setTimerRunning(true), style: { color: "#dc2626", borderColor: "#dc2626" }, "aria-label": "D\xE9marrer le timer", children: timerSeconds > 0 ? "\u25B6" : "\u25B6 Go" }) : /* @__PURE__ */ jsx("button", { className: s.timerBtn, onClick: () => setTimerRunning(false), "aria-label": "Mettre en pause le timer", children: "\u23F8" }),
1239
+ timerSeconds >= 60 && !timerRunning && /* @__PURE__ */ jsxs("button", { className: s.timerBtn, onClick: () => {
1240
+ handleTimerSave();
1241
+ localStorage.removeItem(`timer-sec-${ticketId}`);
1242
+ localStorage.removeItem(`timer-run-${ticketId}`);
1243
+ }, style: { color: "#16a34a", borderColor: "#16a34a" }, "aria-label": "Sauvegarder le temps", children: [
1244
+ "\u{1F4BE} ",
1245
+ Math.round(timerSeconds / 60),
1246
+ "m"
1247
+ ] })
1248
+ ] }),
1249
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, marginTop: 8, alignItems: "center" }, children: [
1250
+ /* @__PURE__ */ 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" }),
1251
+ /* @__PURE__ */ jsx("button", { className: s.timerBtn, onClick: async () => {
1252
+ const input = document.getElementById("manual-time-input");
1253
+ const mins = Number(input?.value);
1254
+ if (!mins || mins < 1 || !ticketId) return;
1255
+ try {
1256
+ 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" }) });
1257
+ if (input) input.value = "";
1258
+ fetchAll();
1259
+ } catch {
1260
+ }
1261
+ }, style: { fontSize: 11 }, children: "+ Ajouter" })
1262
+ ] }),
1263
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "var(--theme-elevation-500)" }, children: [
1264
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "2px 0", alignItems: "center" }, children: [
1265
+ /* @__PURE__ */ jsx("span", { children: "Facturable" }),
1266
+ /* @__PURE__ */ jsx(
1267
+ "button",
1268
+ {
1269
+ onClick: async () => {
1270
+ const newVal = ticket.billable === false ? true : false;
1271
+ try {
1272
+ await fetch(`/api/tickets/${ticketId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ billable: newVal }) });
1273
+ fetchAll();
1274
+ } catch {
1275
+ }
1276
+ },
1277
+ style: { fontWeight: 600, color: ticket.billable !== false ? "#16a34a" : "#dc2626", background: "none", border: "none", cursor: "pointer", fontSize: 11, textDecoration: "underline" },
1278
+ children: ticket.billable !== false ? "Oui" : "Non"
1067
1279
  }
1068
- },
1069
- onBlur: handleAddTag,
1070
- placeholder: "Tag...",
1071
- autoFocus: true
1072
- }
1073
- ) : /* @__PURE__ */ jsx("button", { className: s.tagAddBtn, onClick: () => setAddingTag(true), "aria-label": "Ajouter un tag", children: "+ Tag" })
1074
- ] })
1075
- ] }),
1076
- /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1077
- /* @__PURE__ */ jsx("div", { className: s.sideSectionTitle, children: "Facturation" }),
1078
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
1079
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1080
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12 }, children: "Type" }),
1081
- /* @__PURE__ */ jsxs(
1082
- "select",
1280
+ )
1281
+ ] }),
1282
+ totalMin > 0 && /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "2px 0" }, children: [
1283
+ /* @__PURE__ */ jsx("span", { children: "Montant estim\xE9" }),
1284
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 700, color: "var(--theme-text)" }, children: [
1285
+ (totalMin / 60 * 60).toFixed(0),
1286
+ "\u20AC"
1287
+ ] })
1288
+ ] })
1289
+ ] }),
1290
+ timeEntries.length > 0 && /* @__PURE__ */ jsx("div", { style: { marginTop: 8, fontSize: 11 }, children: timeEntries.slice(0, 6).map((e) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "3px 0", color: "var(--theme-elevation-500)" }, children: [
1291
+ /* @__PURE__ */ jsx("span", { children: new Date(e.date).toLocaleDateString("fr-FR", { day: "numeric", month: "short" }) }),
1292
+ /* @__PURE__ */ jsxs("span", { title: e.description, style: { fontWeight: 600, cursor: e.description ? "help" : "default" }, children: [
1293
+ e.duration,
1294
+ "min"
1295
+ ] })
1296
+ ] }, e.id)) })
1297
+ ] }),
1298
+ /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1299
+ /* @__PURE__ */ jsx("div", { className: s.sideSectionTitle, children: t("detail.tags") }),
1300
+ /* @__PURE__ */ jsxs("div", { className: s.tagsWrap, children: [
1301
+ tags.map((tag) => /* @__PURE__ */ jsxs("span", { className: s.tagChip, children: [
1302
+ tag,
1303
+ /* @__PURE__ */ jsx("button", { className: s.tagRemove, "aria-label": `Retirer le tag ${tag}`, onClick: () => handleRemoveTag(tag), children: "\xD7" })
1304
+ ] }, tag)),
1305
+ addingTag ? /* @__PURE__ */ jsx(
1306
+ "input",
1083
1307
  {
1084
- value: ticket?.billingType || "hourly",
1085
- onChange: async (e) => {
1086
- try {
1087
- await fetch(`/api/tickets/${ticketId}`, {
1088
- method: "PATCH",
1089
- credentials: "include",
1090
- headers: { "Content-Type": "application/json" },
1091
- body: JSON.stringify({ billingType: e.target.value })
1092
- });
1093
- fetchAll();
1094
- } catch {
1308
+ className: s.tagInput,
1309
+ value: newTagValue,
1310
+ onChange: (e) => setNewTagValue(e.target.value),
1311
+ onKeyDown: (e) => {
1312
+ if (e.key === "Enter") handleAddTag();
1313
+ if (e.key === "Escape") {
1314
+ setAddingTag(false);
1315
+ setNewTagValue("");
1095
1316
  }
1096
1317
  },
1097
- style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", background: "#fff" },
1098
- children: [
1099
- /* @__PURE__ */ jsx("option", { value: "hourly", children: "Au temps" }),
1100
- /* @__PURE__ */ jsx("option", { value: "flat", children: "Forfait" })
1101
- ]
1318
+ onBlur: handleAddTag,
1319
+ placeholder: "Tag...",
1320
+ autoFocus: true
1102
1321
  }
1103
- )
1104
- ] }),
1105
- ticket?.billingType === "flat" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1106
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12 }, children: "Montant forfait" }),
1107
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1108
- /* @__PURE__ */ jsx(
1109
- "input",
1322
+ ) : /* @__PURE__ */ jsx("button", { className: s.tagAddBtn, onClick: () => setAddingTag(true), "aria-label": "Ajouter un tag", children: "+ Tag" })
1323
+ ] })
1324
+ ] }),
1325
+ /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1326
+ /* @__PURE__ */ jsx("div", { className: s.sideSectionTitle, children: "Facturation" }),
1327
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
1328
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1329
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12 }, children: "Type" }),
1330
+ /* @__PURE__ */ jsxs(
1331
+ "select",
1110
1332
  {
1111
- type: "number",
1112
- defaultValue: ticket?.flatRateAmount ?? "",
1113
- placeholder: "0",
1114
- onBlur: async (e) => {
1115
- const val = e.target.value ? Number(e.target.value) : null;
1333
+ value: ticket?.billingType || "hourly",
1334
+ onChange: async (e) => {
1116
1335
  try {
1117
1336
  await fetch(`/api/tickets/${ticketId}`, {
1118
1337
  method: "PATCH",
1119
1338
  credentials: "include",
1120
1339
  headers: { "Content-Type": "application/json" },
1121
- body: JSON.stringify({ flatRateAmount: val })
1340
+ body: JSON.stringify({ billingType: e.target.value })
1122
1341
  });
1123
1342
  fetchAll();
1124
1343
  } catch {
1125
1344
  }
1126
1345
  },
1127
- style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", width: 80, textAlign: "right" }
1346
+ style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", background: "#fff" },
1347
+ children: [
1348
+ /* @__PURE__ */ jsx("option", { value: "hourly", children: "Au temps" }),
1349
+ /* @__PURE__ */ jsx("option", { value: "flat", children: "Forfait" })
1350
+ ]
1128
1351
  }
1129
- ),
1130
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#9ca3af" }, children: "\u20AC" })
1131
- ] })
1132
- ] }),
1133
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1134
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12 }, children: "Montant factur\xE9" }),
1135
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1136
- /* @__PURE__ */ jsx(
1137
- "input",
1352
+ )
1353
+ ] }),
1354
+ ticket?.billingType === "flat" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1355
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12 }, children: "Montant forfait" }),
1356
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1357
+ /* @__PURE__ */ jsx(
1358
+ "input",
1359
+ {
1360
+ type: "number",
1361
+ defaultValue: ticket?.flatRateAmount ?? "",
1362
+ placeholder: "0",
1363
+ onBlur: async (e) => {
1364
+ const val = e.target.value ? Number(e.target.value) : null;
1365
+ try {
1366
+ await fetch(`/api/tickets/${ticketId}`, {
1367
+ method: "PATCH",
1368
+ credentials: "include",
1369
+ headers: { "Content-Type": "application/json" },
1370
+ body: JSON.stringify({ flatRateAmount: val })
1371
+ });
1372
+ fetchAll();
1373
+ } catch {
1374
+ }
1375
+ },
1376
+ style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", width: 80, textAlign: "right" }
1377
+ }
1378
+ ),
1379
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#9ca3af" }, children: "\u20AC" })
1380
+ ] })
1381
+ ] }),
1382
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1383
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12 }, children: "Montant factur\xE9" }),
1384
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
1385
+ /* @__PURE__ */ jsx(
1386
+ "input",
1387
+ {
1388
+ type: "number",
1389
+ defaultValue: ticket?.billedAmount ?? "",
1390
+ placeholder: "0",
1391
+ onBlur: async (e) => {
1392
+ const val = e.target.value ? Number(e.target.value) : null;
1393
+ try {
1394
+ await fetch(`/api/tickets/${ticketId}`, {
1395
+ method: "PATCH",
1396
+ credentials: "include",
1397
+ headers: { "Content-Type": "application/json" },
1398
+ body: JSON.stringify({ billedAmount: val })
1399
+ });
1400
+ fetchAll();
1401
+ } catch {
1402
+ }
1403
+ },
1404
+ style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", width: 80, textAlign: "right" }
1405
+ }
1406
+ ),
1407
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#9ca3af" }, children: "\u20AC" })
1408
+ ] })
1409
+ ] }),
1410
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1411
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12 }, children: "Paiement" }),
1412
+ /* @__PURE__ */ jsxs(
1413
+ "select",
1138
1414
  {
1139
- type: "number",
1140
- defaultValue: ticket?.billedAmount ?? "",
1141
- placeholder: "0",
1142
- onBlur: async (e) => {
1143
- const val = e.target.value ? Number(e.target.value) : null;
1415
+ value: ticket?.paymentStatus || "unpaid",
1416
+ onChange: async (e) => {
1144
1417
  try {
1145
1418
  await fetch(`/api/tickets/${ticketId}`, {
1146
1419
  method: "PATCH",
1147
1420
  credentials: "include",
1148
1421
  headers: { "Content-Type": "application/json" },
1149
- body: JSON.stringify({ billedAmount: val })
1422
+ body: JSON.stringify({ paymentStatus: e.target.value })
1150
1423
  });
1151
1424
  fetchAll();
1152
1425
  } catch {
1153
1426
  }
1154
1427
  },
1155
- style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", width: 80, textAlign: "right" }
1428
+ style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", background: "#fff" },
1429
+ children: [
1430
+ /* @__PURE__ */ jsx("option", { value: "unpaid", children: "Non pay\xE9" }),
1431
+ /* @__PURE__ */ jsx("option", { value: "partial", children: "Partiel" }),
1432
+ /* @__PURE__ */ jsx("option", { value: "paid", children: "Pay\xE9" })
1433
+ ]
1156
1434
  }
1157
- ),
1158
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#9ca3af" }, children: "\u20AC" })
1435
+ )
1159
1436
  ] })
1160
- ] }),
1161
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1162
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12 }, children: "Paiement" }),
1163
- /* @__PURE__ */ jsxs(
1164
- "select",
1165
- {
1166
- value: ticket?.paymentStatus || "unpaid",
1167
- onChange: async (e) => {
1168
- try {
1169
- await fetch(`/api/tickets/${ticketId}`, {
1170
- method: "PATCH",
1171
- credentials: "include",
1172
- headers: { "Content-Type": "application/json" },
1173
- body: JSON.stringify({ paymentStatus: e.target.value })
1174
- });
1175
- fetchAll();
1176
- } catch {
1177
- }
1178
- },
1179
- style: { fontSize: 12, padding: "4px 8px", borderRadius: 6, border: "1px solid #e5e7eb", background: "#fff" },
1180
- children: [
1181
- /* @__PURE__ */ jsx("option", { value: "unpaid", children: "Non pay\xE9" }),
1182
- /* @__PURE__ */ jsx("option", { value: "partial", children: "Partiel" }),
1183
- /* @__PURE__ */ jsx("option", { value: "paid", children: "Pay\xE9" })
1184
- ]
1185
- }
1186
- )
1187
1437
  ] })
1188
- ] })
1189
- ] }),
1190
- features.timeTracking && /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1191
- /* @__PURE__ */ jsxs("div", { className: s.sideSectionTitle, children: [
1192
- t("detail.time"),
1193
- " ",
1194
- /* @__PURE__ */ 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" })
1195
1438
  ] }),
1196
- /* @__PURE__ */ jsxs("div", { className: s.timer, children: [
1197
- /* @__PURE__ */ jsxs("span", { className: `${s.timerDisplay} ${timerRunning ? s.timerActive : ""}`, children: [
1198
- String(Math.floor(timerSeconds / 60)).padStart(2, "0"),
1199
- ":",
1200
- String(timerSeconds % 60).padStart(2, "0")
1439
+ 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" }) })
1440
+ ] }),
1441
+ sidebarTab === "client" && /* @__PURE__ */ jsxs("div", { role: "tabpanel", id: "sidebar-panel-client", "aria-labelledby": "sidebar-tab-client", children: [
1442
+ client && /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1443
+ /* @__PURE__ */ jsxs("div", { className: s.clientCard, children: [
1444
+ /* @__PURE__ */ jsx("div", { className: s.clientAvatar, children: initials }),
1445
+ /* @__PURE__ */ jsxs("div", { className: s.clientInfo, children: [
1446
+ /* @__PURE__ */ jsxs("div", { className: s.clientName, children: [
1447
+ client.firstName,
1448
+ " ",
1449
+ client.lastName
1450
+ ] }),
1451
+ /* @__PURE__ */ jsx("div", { className: s.clientCompany, children: client.company }),
1452
+ /* @__PURE__ */ jsx("a", { href: `mailto:${client.email}`, className: s.clientEmail, children: client.email }),
1453
+ client.phone && /* @__PURE__ */ jsx("a", { href: `tel:${client.phone}`, className: s.clientEmail, children: client.phone })
1454
+ ] })
1201
1455
  ] }),
1202
- !timerRunning ? /* @__PURE__ */ jsx("button", { className: s.timerBtn, onClick: () => setTimerRunning(true), style: { color: "#dc2626", borderColor: "#dc2626" }, "aria-label": "D\xE9marrer le timer", children: timerSeconds > 0 ? "\u25B6" : "\u25B6 Go" }) : /* @__PURE__ */ jsx("button", { className: s.timerBtn, onClick: () => setTimerRunning(false), "aria-label": "Mettre en pause le timer", children: "\u23F8" }),
1203
- timerSeconds >= 60 && !timerRunning && /* @__PURE__ */ jsxs("button", { className: s.timerBtn, onClick: () => {
1204
- handleTimerSave();
1205
- localStorage.removeItem(`timer-sec-${ticketId}`);
1206
- localStorage.removeItem(`timer-run-${ticketId}`);
1207
- }, style: { color: "#16a34a", borderColor: "#16a34a" }, "aria-label": "Sauvegarder le temps", children: [
1208
- "\u{1F4BE} ",
1209
- Math.round(timerSeconds / 60),
1210
- "m"
1456
+ /* @__PURE__ */ jsxs("div", { className: s.clientActions, children: [
1457
+ /* @__PURE__ */ jsx(Link, { href: `/admin/collections/support-clients/${client.id}`, className: s.smallBtn, children: t("client.clientSheet") }),
1458
+ /* @__PURE__ */ jsx("button", { className: s.smallBtn, onClick: () => window.open(`/api/admin/impersonate?clientId=${client.id}`, "_blank"), children: t("client.clientPortal") })
1211
1459
  ] })
1212
1460
  ] }),
1213
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, marginTop: 8, alignItems: "center" }, children: [
1214
- /* @__PURE__ */ 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" }),
1215
- /* @__PURE__ */ jsx("button", { className: s.timerBtn, onClick: async () => {
1216
- const input = document.getElementById("manual-time-input");
1217
- const mins = Number(input?.value);
1218
- if (!mins || mins < 1 || !ticketId) return;
1219
- try {
1220
- 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" }) });
1221
- if (input) input.value = "";
1222
- fetchAll();
1223
- } catch {
1224
- }
1225
- }, style: { fontSize: 11 }, children: "+ Ajouter" })
1226
- ] }),
1227
- /* @__PURE__ */ jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "var(--theme-elevation-500)" }, children: [
1228
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "2px 0", alignItems: "center" }, children: [
1229
- /* @__PURE__ */ jsx("span", { children: "Facturable" }),
1230
- /* @__PURE__ */ jsx(
1231
- "button",
1232
- {
1233
- onClick: async () => {
1234
- const newVal = ticket.billable === false ? true : false;
1235
- try {
1236
- await fetch(`/api/tickets/${ticketId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ billable: newVal }) });
1237
- fetchAll();
1238
- } catch {
1239
- }
1240
- },
1241
- style: { fontWeight: 600, color: ticket.billable !== false ? "#16a34a" : "#dc2626", background: "none", border: "none", cursor: "pointer", fontSize: 11, textDecoration: "underline" },
1242
- children: ticket.billable !== false ? "Oui" : "Non"
1243
- }
1244
- )
1245
- ] }),
1246
- totalMin > 0 && /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "2px 0" }, children: [
1247
- /* @__PURE__ */ jsx("span", { children: "Montant estim\xE9" }),
1248
- /* @__PURE__ */ jsxs("span", { style: { fontWeight: 700, color: "var(--theme-text)" }, children: [
1249
- (totalMin / 60 * 60).toFixed(0),
1250
- "\u20AC"
1461
+ /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1462
+ /* @__PURE__ */ jsx("div", { className: s.sideSectionTitle, children: t("detail.sidebar.previousTickets") }),
1463
+ previousTickets.length === 0 ? /* @__PURE__ */ jsx("div", { className: s.previousTicketsEmpty, children: "\u2014" }) : /* @__PURE__ */ jsx("ul", { className: s.previousTicketsList, children: previousTickets.map((pt) => /* @__PURE__ */ jsx("li", { className: s.previousTicketsItem, children: /* @__PURE__ */ jsxs(Link, { href: `/admin/collections/tickets/${pt.id}`, className: s.previousTicketsLink, children: [
1464
+ /* @__PURE__ */ jsx("span", { className: s.previousTicketsSubject, children: pt.subject || `#${pt.id}` }),
1465
+ /* @__PURE__ */ jsxs("span", { className: s.previousTicketsMeta, children: [
1466
+ pt.status ? /* @__PURE__ */ jsx("span", { className: s.previousTicketsStatus, children: pt.status }) : null,
1467
+ /* @__PURE__ */ jsx("span", { className: s.previousTicketsDate, children: new Date(pt.createdAt).toLocaleDateString("fr-FR", { day: "numeric", month: "short", year: "numeric" }) })
1251
1468
  ] })
1252
- ] })
1253
- ] }),
1254
- timeEntries.length > 0 && /* @__PURE__ */ jsx("div", { style: { marginTop: 8, fontSize: 11 }, children: timeEntries.slice(0, 6).map((e) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "3px 0", color: "var(--theme-elevation-500)" }, children: [
1255
- /* @__PURE__ */ jsx("span", { children: new Date(e.date).toLocaleDateString("fr-FR", { day: "numeric", month: "short" }) }),
1256
- /* @__PURE__ */ jsxs("span", { title: e.description, style: { fontWeight: 600, cursor: e.description ? "help" : "default" }, children: [
1257
- e.duration,
1258
- "min"
1259
- ] })
1260
- ] }, e.id)) })
1469
+ ] }) }, pt.id)) })
1470
+ ] })
1261
1471
  ] }),
1262
- features.activityLog && /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1263
- /* @__PURE__ */ jsx("div", { className: s.sideSectionTitle, children: /* @__PURE__ */ jsxs("button", { className: s.collapseBtn, onClick: () => setShowActivity(!showActivity), "aria-label": showActivity ? "Masquer le journal" : "Afficher le journal", children: [
1264
- "Activit\xE9 ",
1265
- showActivity ? "\u25BE" : "\u25B8"
1266
- ] }) }),
1267
- showActivity && activityLog.slice(0, 8).map((a) => /* @__PURE__ */ jsxs("div", { className: s.activityItem, children: [
1472
+ sidebarTab === "activity" && /* @__PURE__ */ jsx("div", { role: "tabpanel", id: "sidebar-panel-activity", "aria-labelledby": "sidebar-tab-activity", children: features.activityLog ? /* @__PURE__ */ jsxs("div", { className: s.sideSection, children: [
1473
+ /* @__PURE__ */ jsx("div", { className: s.sideSectionTitle, children: t("detail.activity") }),
1474
+ activityLog.length === 0 ? /* @__PURE__ */ jsx("div", { className: s.previousTicketsEmpty, children: "\u2014" }) : activityLog.slice(0, 30).map((a) => /* @__PURE__ */ jsxs("div", { className: s.activityItem, children: [
1268
1475
  /* @__PURE__ */ jsx("div", { className: s.activityDot, style: { backgroundColor: a.actorType === "admin" ? "#2563eb" : a.actorType === "system" ? "#6b7280" : "#16a34a" } }),
1269
1476
  /* @__PURE__ */ jsxs("div", { className: s.activityContent, children: [
1270
- /* @__PURE__ */ jsx("div", { className: s.activityText, children: (a.detail || a.action).slice(0, 60) }),
1477
+ /* @__PURE__ */ jsx("div", { className: s.activityText, children: (a.detail || a.action).slice(0, 120) }),
1271
1478
  /* @__PURE__ */ jsx("div", { className: s.activityTime, children: timeAgo(a.createdAt) })
1272
1479
  ] })
1273
1480
  ] }, a.id))
1274
- ] }),
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" }) })
1481
+ ] }) : /* @__PURE__ */ jsx("div", { className: s.sideSection, children: /* @__PURE__ */ jsx("div", { className: s.previousTicketsEmpty, children: "\u2014" }) }) })
1276
1482
  ] })
1277
1483
  ] }),
1278
1484
  undoToast && /* @__PURE__ */ jsxs("div", { className: s.undoToast, role: "alert", children: [