@consilioweb/payload-support 0.14.0 → 0.16.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.
@@ -95,6 +95,77 @@ function ExtMessagePanel({
95
95
  /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleSendExtMsg, disabled: sendingExtMsg || !extMsgBody.trim(), style: constants.s.btn("#818cf8", sendingExtMsg || !extMsgBody.trim()), children: sendingExtMsg ? "Ajout..." : "Ajouter (sans notification)" })
96
96
  ] });
97
97
  }
98
+ const REMINDER_DELAYS = [
99
+ { hours: 24, label: "24 h" },
100
+ { hours: 48, label: "48 h" },
101
+ { hours: 72, label: "72 h" }
102
+ ];
103
+ function ReminderPanel({
104
+ clientFirstName,
105
+ clientEmail,
106
+ reminderHours,
107
+ setReminderHours,
108
+ reminderSending,
109
+ reminderError,
110
+ handleSendReminder
111
+ }) {
112
+ const deadline = new Date(Date.now() + reminderHours * 60 * 60 * 1e3);
113
+ const deadlineLabel = deadline.toLocaleString("fr-FR", {
114
+ weekday: "long",
115
+ day: "numeric",
116
+ month: "long",
117
+ hour: "2-digit",
118
+ minute: "2-digit"
119
+ });
120
+ const name = clientFirstName?.trim() || "le client";
121
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "14px 18px", borderRadius: "8px", backgroundColor: "#fff7ed", border: "1px solid #fed7aa", marginBottom: "14px" }, children: [
122
+ /* @__PURE__ */ jsxRuntime.jsxs("h4", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px", color: "#9a3412" }, children: [
123
+ "Relancer ",
124
+ name,
125
+ " \u2014 fermeture automatique sans r\xE9ponse"
126
+ ] }),
127
+ clientEmail ? /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: "12px", color: "#7c2d12", margin: "0 0 12px 0", lineHeight: 1.5 }, children: [
128
+ "Un email de relance g\xE9n\xE9rique sera envoy\xE9 \xE0 ",
129
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: clientEmail }),
130
+ ". Sans retour de sa part avant l'\xE9ch\xE9ance, le ticket sera automatiquement r\xE9solu (via le cron auto-close)."
131
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "12px", color: "#b91c1c", margin: "0 0 12px 0", fontWeight: 600 }, children: "Ce client n'a pas d'adresse email : impossible d'envoyer une relance." }),
132
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "6px", alignItems: "center", flexWrap: "wrap", marginBottom: "12px" }, children: [
133
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "12px", fontWeight: 600, color: "#9a3412" }, children: "Fermer dans :" }),
134
+ REMINDER_DELAYS.map((d) => {
135
+ const active = reminderHours === d.hours;
136
+ return /* @__PURE__ */ jsxRuntime.jsx(
137
+ "button",
138
+ {
139
+ type: "button",
140
+ onClick: () => setReminderHours(d.hours),
141
+ style: {
142
+ ...active ? constants.s.btn(constants.C.orange) : constants.s.outlineBtn("#ea580c"),
143
+ fontSize: "12px",
144
+ padding: "6px 14px"
145
+ },
146
+ children: d.label
147
+ },
148
+ d.hours
149
+ );
150
+ })
151
+ ] }),
152
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: "12px", color: "#7c2d12", marginBottom: "12px" }, children: [
153
+ "Fermeture automatique pr\xE9vue le ",
154
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: deadlineLabel }),
155
+ " sans r\xE9ponse."
156
+ ] }),
157
+ reminderError && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: "12px", color: "#b91c1c", fontWeight: 600, marginBottom: "10px" }, children: reminderError }),
158
+ /* @__PURE__ */ jsxRuntime.jsx(
159
+ "button",
160
+ {
161
+ onClick: handleSendReminder,
162
+ disabled: reminderSending || !clientEmail,
163
+ style: { ...constants.s.btn(constants.C.orange, reminderSending || !clientEmail), color: "#fff", fontSize: "13px", padding: "8px 18px" },
164
+ children: reminderSending ? "Envoi..." : "Confirmer la relance"
165
+ }
166
+ )
167
+ ] });
168
+ }
98
169
  function SnoozePanel({ snoozeSaving, handleSnooze }) {
99
170
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "14px 18px", borderRadius: "8px", backgroundColor: "#faf5ff", border: "1px solid #e9d5ff", marginBottom: "14px" }, children: [
100
171
  /* @__PURE__ */ jsxRuntime.jsx("h4", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "10px", color: "#5b21b6" }, children: "Snooze \u2014 masquer temporairement" }),
@@ -118,4 +189,5 @@ function SnoozePanel({ snoozeSaving, handleSnooze }) {
118
189
 
119
190
  exports.ExtMessagePanel = ExtMessagePanel;
120
191
  exports.MergePanel = MergePanel;
192
+ exports.ReminderPanel = ReminderPanel;
121
193
  exports.SnoozePanel = SnoozePanel;
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
3
  import { useRef } from 'react';
4
- import { s } from '../constants.js';
4
+ import { s, C } from '../constants.js';
5
5
 
6
6
  function MergePanel({
7
7
  mergeTarget,
@@ -94,6 +94,77 @@ function ExtMessagePanel({
94
94
  /* @__PURE__ */ jsx("button", { onClick: handleSendExtMsg, disabled: sendingExtMsg || !extMsgBody.trim(), style: s.btn("#818cf8", sendingExtMsg || !extMsgBody.trim()), children: sendingExtMsg ? "Ajout..." : "Ajouter (sans notification)" })
95
95
  ] });
96
96
  }
97
+ const REMINDER_DELAYS = [
98
+ { hours: 24, label: "24 h" },
99
+ { hours: 48, label: "48 h" },
100
+ { hours: 72, label: "72 h" }
101
+ ];
102
+ function ReminderPanel({
103
+ clientFirstName,
104
+ clientEmail,
105
+ reminderHours,
106
+ setReminderHours,
107
+ reminderSending,
108
+ reminderError,
109
+ handleSendReminder
110
+ }) {
111
+ const deadline = new Date(Date.now() + reminderHours * 60 * 60 * 1e3);
112
+ const deadlineLabel = deadline.toLocaleString("fr-FR", {
113
+ weekday: "long",
114
+ day: "numeric",
115
+ month: "long",
116
+ hour: "2-digit",
117
+ minute: "2-digit"
118
+ });
119
+ const name = clientFirstName?.trim() || "le client";
120
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "14px 18px", borderRadius: "8px", backgroundColor: "#fff7ed", border: "1px solid #fed7aa", marginBottom: "14px" }, children: [
121
+ /* @__PURE__ */ jsxs("h4", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px", color: "#9a3412" }, children: [
122
+ "Relancer ",
123
+ name,
124
+ " \u2014 fermeture automatique sans r\xE9ponse"
125
+ ] }),
126
+ clientEmail ? /* @__PURE__ */ jsxs("p", { style: { fontSize: "12px", color: "#7c2d12", margin: "0 0 12px 0", lineHeight: 1.5 }, children: [
127
+ "Un email de relance g\xE9n\xE9rique sera envoy\xE9 \xE0 ",
128
+ /* @__PURE__ */ jsx("strong", { children: clientEmail }),
129
+ ". Sans retour de sa part avant l'\xE9ch\xE9ance, le ticket sera automatiquement r\xE9solu (via le cron auto-close)."
130
+ ] }) : /* @__PURE__ */ jsx("p", { style: { fontSize: "12px", color: "#b91c1c", margin: "0 0 12px 0", fontWeight: 600 }, children: "Ce client n'a pas d'adresse email : impossible d'envoyer une relance." }),
131
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "6px", alignItems: "center", flexWrap: "wrap", marginBottom: "12px" }, children: [
132
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "12px", fontWeight: 600, color: "#9a3412" }, children: "Fermer dans :" }),
133
+ REMINDER_DELAYS.map((d) => {
134
+ const active = reminderHours === d.hours;
135
+ return /* @__PURE__ */ jsx(
136
+ "button",
137
+ {
138
+ type: "button",
139
+ onClick: () => setReminderHours(d.hours),
140
+ style: {
141
+ ...active ? s.btn(C.orange) : s.outlineBtn("#ea580c"),
142
+ fontSize: "12px",
143
+ padding: "6px 14px"
144
+ },
145
+ children: d.label
146
+ },
147
+ d.hours
148
+ );
149
+ })
150
+ ] }),
151
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: "12px", color: "#7c2d12", marginBottom: "12px" }, children: [
152
+ "Fermeture automatique pr\xE9vue le ",
153
+ /* @__PURE__ */ jsx("strong", { children: deadlineLabel }),
154
+ " sans r\xE9ponse."
155
+ ] }),
156
+ reminderError && /* @__PURE__ */ jsx("div", { style: { fontSize: "12px", color: "#b91c1c", fontWeight: 600, marginBottom: "10px" }, children: reminderError }),
157
+ /* @__PURE__ */ jsx(
158
+ "button",
159
+ {
160
+ onClick: handleSendReminder,
161
+ disabled: reminderSending || !clientEmail,
162
+ style: { ...s.btn(C.orange, reminderSending || !clientEmail), color: "#fff", fontSize: "13px", padding: "8px 18px" },
163
+ children: reminderSending ? "Envoi..." : "Confirmer la relance"
164
+ }
165
+ )
166
+ ] });
167
+ }
97
168
  function SnoozePanel({ snoozeSaving, handleSnooze }) {
98
169
  return /* @__PURE__ */ jsxs("div", { style: { padding: "14px 18px", borderRadius: "8px", backgroundColor: "#faf5ff", border: "1px solid #e9d5ff", marginBottom: "14px" }, children: [
99
170
  /* @__PURE__ */ jsx("h4", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "10px", color: "#5b21b6" }, children: "Snooze \u2014 masquer temporairement" }),
@@ -115,4 +186,4 @@ function SnoozePanel({ snoozeSaving, handleSnooze }) {
115
186
  ] });
116
187
  }
117
188
 
118
- export { ExtMessagePanel, MergePanel, SnoozePanel };
189
+ export { ExtMessagePanel, MergePanel, ReminderPanel, SnoozePanel };
@@ -17,11 +17,18 @@ function QuickActions({
17
17
  onToggleExtMsg,
18
18
  onToggleSnooze,
19
19
  onNextTicket,
20
+ showReminderButton,
21
+ onToggleReminder,
22
+ autoCloseScheduledAt,
23
+ onCancelScheduledClose,
24
+ cancelingClose,
25
+ reminderSuccess,
20
26
  showNextTicket,
21
27
  nextTicketId,
22
28
  nextTicketInfo,
23
29
  onCloseNextTicket
24
30
  }) {
31
+ const scheduledClose = autoCloseScheduledAt && new Date(autoCloseScheduledAt) > /* @__PURE__ */ new Date() ? new Date(autoCloseScheduledAt).toLocaleString("fr-FR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }) : null;
25
32
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
26
33
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap", marginBottom: "16px", alignItems: "center" }, children: [
27
34
  statusTransitions.map((a) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -39,7 +46,21 @@ function QuickActions({
39
46
  /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onToggleMerge, style: constants.s.ghostBtn("#be185d"), children: "Fusionner" }),
40
47
  /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onToggleExtMsg, style: constants.s.ghostBtn("#4f46e5"), children: "+ Message re\xE7u" }),
41
48
  /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onToggleSnooze, style: constants.s.ghostBtn("#7c3aed"), children: "Snooze" }),
42
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onNextTicket, style: constants.s.ghostBtn("#16a34a"), children: "Ticket suivant" })
49
+ showReminderButton && /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: onToggleReminder, style: constants.s.ghostBtn("#ea580c"), children: [
50
+ "\u23F0",
51
+ " Relancer"
52
+ ] }),
53
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onNextTicket, style: constants.s.ghostBtn("#16a34a"), children: "Ticket suivant" }),
54
+ scheduledClose && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: "8px", padding: "5px 10px", borderRadius: "6px", backgroundColor: "#fff7ed", border: "1px solid #fed7aa", fontSize: "12px", color: "#9a3412", fontWeight: 600 }, children: [
55
+ "\u23F0",
56
+ " Fermeture auto le ",
57
+ scheduledClose,
58
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onCancelScheduledClose, disabled: cancelingClose, style: { border: "none", background: "none", color: "#b91c1c", cursor: cancelingClose ? "not-allowed" : "pointer", fontWeight: 700, fontSize: "12px", textDecoration: "underline", padding: 0, opacity: cancelingClose ? 0.5 : 1 }, children: cancelingClose ? "..." : "Annuler" })
59
+ ] }),
60
+ reminderSuccess && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...constants.s.badge("#f0fdf4", "#16a34a"), fontSize: "12px", padding: "5px 10px" }, children: [
61
+ "Relance envoy\xE9e ",
62
+ "\u2713"
63
+ ] })
43
64
  ] }),
44
65
  showNextTicket && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "10px 14px", borderRadius: "8px", backgroundColor: "#f0fdf4", border: `1px solid ${constants.C.border}`, marginBottom: "14px", display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
45
66
  nextTicketId ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -16,11 +16,18 @@ function QuickActions({
16
16
  onToggleExtMsg,
17
17
  onToggleSnooze,
18
18
  onNextTicket,
19
+ showReminderButton,
20
+ onToggleReminder,
21
+ autoCloseScheduledAt,
22
+ onCancelScheduledClose,
23
+ cancelingClose,
24
+ reminderSuccess,
19
25
  showNextTicket,
20
26
  nextTicketId,
21
27
  nextTicketInfo,
22
28
  onCloseNextTicket
23
29
  }) {
30
+ const scheduledClose = autoCloseScheduledAt && new Date(autoCloseScheduledAt) > /* @__PURE__ */ new Date() ? new Date(autoCloseScheduledAt).toLocaleString("fr-FR", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }) : null;
24
31
  return /* @__PURE__ */ jsxs(Fragment, { children: [
25
32
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap", marginBottom: "16px", alignItems: "center" }, children: [
26
33
  statusTransitions.map((a) => /* @__PURE__ */ jsx(
@@ -38,7 +45,21 @@ function QuickActions({
38
45
  /* @__PURE__ */ jsx("button", { onClick: onToggleMerge, style: s.ghostBtn("#be185d"), children: "Fusionner" }),
39
46
  /* @__PURE__ */ jsx("button", { onClick: onToggleExtMsg, style: s.ghostBtn("#4f46e5"), children: "+ Message re\xE7u" }),
40
47
  /* @__PURE__ */ jsx("button", { onClick: onToggleSnooze, style: s.ghostBtn("#7c3aed"), children: "Snooze" }),
41
- /* @__PURE__ */ jsx("button", { onClick: onNextTicket, style: s.ghostBtn("#16a34a"), children: "Ticket suivant" })
48
+ showReminderButton && /* @__PURE__ */ jsxs("button", { onClick: onToggleReminder, style: s.ghostBtn("#ea580c"), children: [
49
+ "\u23F0",
50
+ " Relancer"
51
+ ] }),
52
+ /* @__PURE__ */ jsx("button", { onClick: onNextTicket, style: s.ghostBtn("#16a34a"), children: "Ticket suivant" }),
53
+ scheduledClose && /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: "8px", padding: "5px 10px", borderRadius: "6px", backgroundColor: "#fff7ed", border: "1px solid #fed7aa", fontSize: "12px", color: "#9a3412", fontWeight: 600 }, children: [
54
+ "\u23F0",
55
+ " Fermeture auto le ",
56
+ scheduledClose,
57
+ /* @__PURE__ */ jsx("button", { onClick: onCancelScheduledClose, disabled: cancelingClose, style: { border: "none", background: "none", color: "#b91c1c", cursor: cancelingClose ? "not-allowed" : "pointer", fontWeight: 700, fontSize: "12px", textDecoration: "underline", padding: 0, opacity: cancelingClose ? 0.5 : 1 }, children: cancelingClose ? "..." : "Annuler" })
58
+ ] }),
59
+ reminderSuccess && /* @__PURE__ */ jsxs("span", { style: { ...s.badge("#f0fdf4", "#16a34a"), fontSize: "12px", padding: "5px 10px" }, children: [
60
+ "Relance envoy\xE9e ",
61
+ "\u2713"
62
+ ] })
42
63
  ] }),
43
64
  showNextTicket && /* @__PURE__ */ jsxs("div", { style: { padding: "10px 14px", borderRadius: "8px", backgroundColor: "#f0fdf4", border: `1px solid ${C.border}`, marginBottom: "14px", display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
44
65
  nextTicketId ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -19,6 +19,12 @@ function useTicketActions(id, fetchAll) {
19
19
  const [showSnooze, setShowSnooze] = react.useState(false);
20
20
  const [snoozeUntil, setSnoozeUntil] = react.useState(null);
21
21
  const [snoozeSaving, setSnoozeSaving] = react.useState(false);
22
+ const [showReminder, setShowReminder] = react.useState(false);
23
+ const [reminderHours, setReminderHours] = react.useState(24);
24
+ const [reminderSending, setReminderSending] = react.useState(false);
25
+ const [reminderError, setReminderError] = react.useState("");
26
+ const [reminderSuccess, setReminderSuccess] = react.useState(false);
27
+ const [cancelingClose, setCancelingClose] = react.useState(false);
22
28
  const [showNextTicket, setShowNextTicket] = react.useState(false);
23
29
  const [nextTicketId, setNextTicketId] = react.useState(null);
24
30
  const [nextTicketInfo, setNextTicketInfo] = react.useState("");
@@ -170,6 +176,48 @@ function useTicketActions(id, fetchAll) {
170
176
  setSnoozeSaving(false);
171
177
  }
172
178
  };
179
+ const handleSendReminder = async () => {
180
+ if (!id) return;
181
+ setReminderSending(true);
182
+ setReminderError("");
183
+ try {
184
+ const res = await fetch("/api/support/send-reminder", {
185
+ method: "POST",
186
+ headers: { "Content-Type": "application/json" },
187
+ credentials: "include",
188
+ body: JSON.stringify({ ticketId: id, hours: reminderHours })
189
+ });
190
+ const d = await res.json().catch(() => ({}));
191
+ if (res.ok) {
192
+ setShowReminder(false);
193
+ setReminderSuccess(true);
194
+ setTimeout(() => setReminderSuccess(false), 5e3);
195
+ fetchAll();
196
+ } else {
197
+ setReminderError(d.error || "Erreur lors de la relance");
198
+ }
199
+ } catch {
200
+ setReminderError("Erreur r\xE9seau");
201
+ } finally {
202
+ setReminderSending(false);
203
+ }
204
+ };
205
+ const handleCancelScheduledClose = async () => {
206
+ if (!id) return;
207
+ setCancelingClose(true);
208
+ try {
209
+ await fetch(`/api/tickets/${id}`, {
210
+ method: "PATCH",
211
+ headers: { "Content-Type": "application/json" },
212
+ credentials: "include",
213
+ body: JSON.stringify({ autoCloseScheduledAt: null, autoCloseRemindedAt: null })
214
+ });
215
+ fetchAll();
216
+ } catch {
217
+ } finally {
218
+ setCancelingClose(false);
219
+ }
220
+ };
173
221
  const handleNextTicket = async () => {
174
222
  try {
175
223
  const res = await fetch("/api/tickets?where[status][equals]=open&sort=updatedAt&limit=1&depth=0", { credentials: "include" });
@@ -220,6 +268,17 @@ function useTicketActions(id, fetchAll) {
220
268
  setSnoozeUntil,
221
269
  snoozeSaving,
222
270
  handleSnooze,
271
+ showReminder,
272
+ setShowReminder,
273
+ reminderHours,
274
+ setReminderHours,
275
+ reminderSending,
276
+ reminderError,
277
+ setReminderError,
278
+ reminderSuccess,
279
+ handleSendReminder,
280
+ cancelingClose,
281
+ handleCancelScheduledClose,
223
282
  showNextTicket,
224
283
  setShowNextTicket,
225
284
  nextTicketId,
@@ -18,6 +18,12 @@ function useTicketActions(id, fetchAll) {
18
18
  const [showSnooze, setShowSnooze] = useState(false);
19
19
  const [snoozeUntil, setSnoozeUntil] = useState(null);
20
20
  const [snoozeSaving, setSnoozeSaving] = useState(false);
21
+ const [showReminder, setShowReminder] = useState(false);
22
+ const [reminderHours, setReminderHours] = useState(24);
23
+ const [reminderSending, setReminderSending] = useState(false);
24
+ const [reminderError, setReminderError] = useState("");
25
+ const [reminderSuccess, setReminderSuccess] = useState(false);
26
+ const [cancelingClose, setCancelingClose] = useState(false);
21
27
  const [showNextTicket, setShowNextTicket] = useState(false);
22
28
  const [nextTicketId, setNextTicketId] = useState(null);
23
29
  const [nextTicketInfo, setNextTicketInfo] = useState("");
@@ -169,6 +175,48 @@ function useTicketActions(id, fetchAll) {
169
175
  setSnoozeSaving(false);
170
176
  }
171
177
  };
178
+ const handleSendReminder = async () => {
179
+ if (!id) return;
180
+ setReminderSending(true);
181
+ setReminderError("");
182
+ try {
183
+ const res = await fetch("/api/support/send-reminder", {
184
+ method: "POST",
185
+ headers: { "Content-Type": "application/json" },
186
+ credentials: "include",
187
+ body: JSON.stringify({ ticketId: id, hours: reminderHours })
188
+ });
189
+ const d = await res.json().catch(() => ({}));
190
+ if (res.ok) {
191
+ setShowReminder(false);
192
+ setReminderSuccess(true);
193
+ setTimeout(() => setReminderSuccess(false), 5e3);
194
+ fetchAll();
195
+ } else {
196
+ setReminderError(d.error || "Erreur lors de la relance");
197
+ }
198
+ } catch {
199
+ setReminderError("Erreur r\xE9seau");
200
+ } finally {
201
+ setReminderSending(false);
202
+ }
203
+ };
204
+ const handleCancelScheduledClose = async () => {
205
+ if (!id) return;
206
+ setCancelingClose(true);
207
+ try {
208
+ await fetch(`/api/tickets/${id}`, {
209
+ method: "PATCH",
210
+ headers: { "Content-Type": "application/json" },
211
+ credentials: "include",
212
+ body: JSON.stringify({ autoCloseScheduledAt: null, autoCloseRemindedAt: null })
213
+ });
214
+ fetchAll();
215
+ } catch {
216
+ } finally {
217
+ setCancelingClose(false);
218
+ }
219
+ };
172
220
  const handleNextTicket = async () => {
173
221
  try {
174
222
  const res = await fetch("/api/tickets?where[status][equals]=open&sort=updatedAt&limit=1&depth=0", { credentials: "include" });
@@ -219,6 +267,17 @@ function useTicketActions(id, fetchAll) {
219
267
  setSnoozeUntil,
220
268
  snoozeSaving,
221
269
  handleSnooze,
270
+ showReminder,
271
+ setShowReminder,
272
+ reminderHours,
273
+ setReminderHours,
274
+ reminderSending,
275
+ reminderError,
276
+ setReminderError,
277
+ reminderSuccess,
278
+ handleSendReminder,
279
+ cancelingClose,
280
+ handleCancelScheduledClose,
222
281
  showNextTicket,
223
282
  setShowNextTicket,
224
283
  nextTicketId,
@@ -188,6 +188,7 @@ const TicketConversation = () => {
188
188
  const [ticketSubject, setTicketSubject] = React.useState("");
189
189
  const [ticketSource, setTicketSource] = React.useState("");
190
190
  const [chatSession, setChatSession] = React.useState("");
191
+ const [autoCloseScheduledAt, setAutoCloseScheduledAt] = React.useState(null);
191
192
  const [clientTickets, setClientTickets] = React.useState([]);
192
193
  const [clientProjects, setClientProjects] = React.useState([]);
193
194
  const [clientNotes, setClientNotes] = React.useState("");
@@ -220,6 +221,7 @@ const TicketConversation = () => {
220
221
  setClient(d.client);
221
222
  }
222
223
  setSnoozeUntil(d.snoozeUntil || null);
224
+ setAutoCloseScheduledAt(d.autoCloseScheduledAt || null);
223
225
  setLastClientReadAt(d.lastClientReadAt || null);
224
226
  setCurrentStatus(d.status || "");
225
227
  setTicketNumber(d.ticketNumber || "");
@@ -364,7 +366,7 @@ const TicketConversation = () => {
364
366
  const ma = useMessageActions.useMessageActions(id, client, fetchAll);
365
367
  const { togglingAuthor, editingMsg, editBody, editHtml, setEditHtml, savingEdit, handleEditStart, handleEditSave, handleEditCancel, deletingMsg, handleDelete, resendingMsg, resendSuccess, handleResend, handleToggleAuthor, handleSplitMessage, setEditBody } = ma;
366
368
  const ta = useTicketActions.useTicketActions(id, fetchAll);
367
- const { statusUpdating, handleStatusChange, showMerge, setShowMerge, mergeTarget, setMergeTarget, mergeTargetInfo, setMergeTargetInfo, mergeError, setMergeError, merging, handleMergeLookup, handleMerge, showExtMsg, setShowExtMsg, extMsgBody, setExtMsgBody, extMsgAuthor, setExtMsgAuthor, extMsgDate, setExtMsgDate, extMsgFiles, setExtMsgFiles, sendingExtMsg, handleExtFileChange, handleSendExtMsg, showSnooze, setShowSnooze, snoozeUntil, setSnoozeUntil, snoozeSaving, handleSnooze, showNextTicket, setShowNextTicket, nextTicketId, nextTicketInfo, handleNextTicket } = ta;
369
+ const { statusUpdating, handleStatusChange, showMerge, setShowMerge, mergeTarget, setMergeTarget, mergeTargetInfo, setMergeTargetInfo, mergeError, setMergeError, merging, handleMergeLookup, handleMerge, showExtMsg, setShowExtMsg, extMsgBody, setExtMsgBody, extMsgAuthor, setExtMsgAuthor, extMsgDate, setExtMsgDate, extMsgFiles, setExtMsgFiles, sendingExtMsg, handleExtFileChange, handleSendExtMsg, showSnooze, setShowSnooze, snoozeUntil, setSnoozeUntil, snoozeSaving, handleSnooze, showReminder, setShowReminder, reminderHours, setReminderHours, reminderSending, reminderError, setReminderError, reminderSuccess, handleSendReminder, cancelingClose, handleCancelScheduledClose, showNextTicket, setShowNextTicket, nextTicketId, nextTicketInfo, handleNextTicket } = ta;
368
370
  const replyEditorRef = React.useRef(null);
369
371
  const rp = useReply.useReply(id, client, cannedResponses, ticketNumber, ticketSubject, fetchAll, handleNextTicket, replyEditorRef);
370
372
  const { fileInputRef, replyBody, setReplyBody, replyHtml, setReplyHtml, replyFiles, setReplyFiles, isInternal, setIsInternal, notifyClient, setNotifyClient, sendAsClient, setSendAsClient, sending, showSchedule, setShowSchedule, scheduleDate, setScheduleDate, handleEditorFileUpload, handleCannedSelect, handleReplyFileChange, handleSendReply, handleScheduleReply } = rp;
@@ -393,6 +395,7 @@ const TicketConversation = () => {
393
395
  const d = await ticketRes.json();
394
396
  setCurrentStatus(d.status || "");
395
397
  setSnoozeUntil(d.snoozeUntil || null);
398
+ setAutoCloseScheduledAt(d.autoCloseScheduledAt || null);
396
399
  setLastClientReadAt(d.lastClientReadAt || null);
397
400
  }
398
401
  if (activityRes.ok) {
@@ -1080,18 +1083,33 @@ const TicketConversation = () => {
1080
1083
  setShowMerge(!showMerge);
1081
1084
  setShowExtMsg(false);
1082
1085
  setShowSnooze(false);
1086
+ setShowReminder(false);
1083
1087
  },
1084
1088
  onToggleExtMsg: () => {
1085
1089
  setShowExtMsg(!showExtMsg);
1086
1090
  setShowMerge(false);
1087
1091
  setShowSnooze(false);
1092
+ setShowReminder(false);
1088
1093
  },
1089
1094
  onToggleSnooze: () => {
1090
1095
  setShowSnooze(!showSnooze);
1091
1096
  setShowMerge(false);
1092
1097
  setShowExtMsg(false);
1098
+ setShowReminder(false);
1093
1099
  },
1094
1100
  onNextTicket: handleNextTicket,
1101
+ showReminderButton: features.autoClose,
1102
+ onToggleReminder: () => {
1103
+ setShowReminder(!showReminder);
1104
+ setReminderError("");
1105
+ setShowMerge(false);
1106
+ setShowExtMsg(false);
1107
+ setShowSnooze(false);
1108
+ },
1109
+ autoCloseScheduledAt,
1110
+ onCancelScheduledClose: handleCancelScheduledClose,
1111
+ cancelingClose,
1112
+ reminderSuccess,
1095
1113
  showNextTicket,
1096
1114
  nextTicketId,
1097
1115
  nextTicketInfo,
@@ -1141,7 +1159,19 @@ const TicketConversation = () => {
1141
1159
  handleExtFileChange
1142
1160
  }
1143
1161
  ),
1144
- features.snooze && showSnooze && /* @__PURE__ */ jsxRuntime.jsx(ActionPanels.SnoozePanel, { snoozeSaving, handleSnooze })
1162
+ features.snooze && showSnooze && /* @__PURE__ */ jsxRuntime.jsx(ActionPanels.SnoozePanel, { snoozeSaving, handleSnooze }),
1163
+ features.autoClose && showReminder && /* @__PURE__ */ jsxRuntime.jsx(
1164
+ ActionPanels.ReminderPanel,
1165
+ {
1166
+ clientFirstName: client?.firstName || "",
1167
+ clientEmail: client?.email || "",
1168
+ reminderHours,
1169
+ setReminderHours,
1170
+ reminderSending,
1171
+ reminderError,
1172
+ handleSendReminder
1173
+ }
1174
+ )
1145
1175
  ] }),
1146
1176
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: layoutStyles.sideColumn, children: [
1147
1177
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -9,7 +9,7 @@ import { TicketHeader } from './components/TicketHeader.js';
9
9
  import { ClientBar } from './components/ClientBar.js';
10
10
  import { QuickActions } from './components/QuickActions.js';
11
11
  import { AISummaryPanel } from './components/AISummaryPanel.js';
12
- import { MergePanel, ExtMessagePanel, SnoozePanel } from './components/ActionPanels.js';
12
+ import { MergePanel, ExtMessagePanel, SnoozePanel, ReminderPanel } from './components/ActionPanels.js';
13
13
  import { ActivityLog } from './components/ActivityLog.js';
14
14
  import { ClientHistory } from './components/ClientHistory.js';
15
15
  import { TimeTrackingPanel } from './components/TimeTrackingPanel.js';
@@ -183,6 +183,7 @@ const TicketConversation = () => {
183
183
  const [ticketSubject, setTicketSubject] = useState("");
184
184
  const [ticketSource, setTicketSource] = useState("");
185
185
  const [chatSession, setChatSession] = useState("");
186
+ const [autoCloseScheduledAt, setAutoCloseScheduledAt] = useState(null);
186
187
  const [clientTickets, setClientTickets] = useState([]);
187
188
  const [clientProjects, setClientProjects] = useState([]);
188
189
  const [clientNotes, setClientNotes] = useState("");
@@ -215,6 +216,7 @@ const TicketConversation = () => {
215
216
  setClient(d.client);
216
217
  }
217
218
  setSnoozeUntil(d.snoozeUntil || null);
219
+ setAutoCloseScheduledAt(d.autoCloseScheduledAt || null);
218
220
  setLastClientReadAt(d.lastClientReadAt || null);
219
221
  setCurrentStatus(d.status || "");
220
222
  setTicketNumber(d.ticketNumber || "");
@@ -359,7 +361,7 @@ const TicketConversation = () => {
359
361
  const ma = useMessageActions(id, client, fetchAll);
360
362
  const { togglingAuthor, editingMsg, editBody, editHtml, setEditHtml, savingEdit, handleEditStart, handleEditSave, handleEditCancel, deletingMsg, handleDelete, resendingMsg, resendSuccess, handleResend, handleToggleAuthor, handleSplitMessage, setEditBody } = ma;
361
363
  const ta = useTicketActions(id, fetchAll);
362
- const { statusUpdating, handleStatusChange, showMerge, setShowMerge, mergeTarget, setMergeTarget, mergeTargetInfo, setMergeTargetInfo, mergeError, setMergeError, merging, handleMergeLookup, handleMerge, showExtMsg, setShowExtMsg, extMsgBody, setExtMsgBody, extMsgAuthor, setExtMsgAuthor, extMsgDate, setExtMsgDate, extMsgFiles, setExtMsgFiles, sendingExtMsg, handleExtFileChange, handleSendExtMsg, showSnooze, setShowSnooze, snoozeUntil, setSnoozeUntil, snoozeSaving, handleSnooze, showNextTicket, setShowNextTicket, nextTicketId, nextTicketInfo, handleNextTicket } = ta;
364
+ const { statusUpdating, handleStatusChange, showMerge, setShowMerge, mergeTarget, setMergeTarget, mergeTargetInfo, setMergeTargetInfo, mergeError, setMergeError, merging, handleMergeLookup, handleMerge, showExtMsg, setShowExtMsg, extMsgBody, setExtMsgBody, extMsgAuthor, setExtMsgAuthor, extMsgDate, setExtMsgDate, extMsgFiles, setExtMsgFiles, sendingExtMsg, handleExtFileChange, handleSendExtMsg, showSnooze, setShowSnooze, snoozeUntil, setSnoozeUntil, snoozeSaving, handleSnooze, showReminder, setShowReminder, reminderHours, setReminderHours, reminderSending, reminderError, setReminderError, reminderSuccess, handleSendReminder, cancelingClose, handleCancelScheduledClose, showNextTicket, setShowNextTicket, nextTicketId, nextTicketInfo, handleNextTicket } = ta;
363
365
  const replyEditorRef = useRef(null);
364
366
  const rp = useReply(id, client, cannedResponses, ticketNumber, ticketSubject, fetchAll, handleNextTicket, replyEditorRef);
365
367
  const { fileInputRef, replyBody, setReplyBody, replyHtml, setReplyHtml, replyFiles, setReplyFiles, isInternal, setIsInternal, notifyClient, setNotifyClient, sendAsClient, setSendAsClient, sending, showSchedule, setShowSchedule, scheduleDate, setScheduleDate, handleEditorFileUpload, handleCannedSelect, handleReplyFileChange, handleSendReply, handleScheduleReply } = rp;
@@ -388,6 +390,7 @@ const TicketConversation = () => {
388
390
  const d = await ticketRes.json();
389
391
  setCurrentStatus(d.status || "");
390
392
  setSnoozeUntil(d.snoozeUntil || null);
393
+ setAutoCloseScheduledAt(d.autoCloseScheduledAt || null);
391
394
  setLastClientReadAt(d.lastClientReadAt || null);
392
395
  }
393
396
  if (activityRes.ok) {
@@ -1075,18 +1078,33 @@ const TicketConversation = () => {
1075
1078
  setShowMerge(!showMerge);
1076
1079
  setShowExtMsg(false);
1077
1080
  setShowSnooze(false);
1081
+ setShowReminder(false);
1078
1082
  },
1079
1083
  onToggleExtMsg: () => {
1080
1084
  setShowExtMsg(!showExtMsg);
1081
1085
  setShowMerge(false);
1082
1086
  setShowSnooze(false);
1087
+ setShowReminder(false);
1083
1088
  },
1084
1089
  onToggleSnooze: () => {
1085
1090
  setShowSnooze(!showSnooze);
1086
1091
  setShowMerge(false);
1087
1092
  setShowExtMsg(false);
1093
+ setShowReminder(false);
1088
1094
  },
1089
1095
  onNextTicket: handleNextTicket,
1096
+ showReminderButton: features.autoClose,
1097
+ onToggleReminder: () => {
1098
+ setShowReminder(!showReminder);
1099
+ setReminderError("");
1100
+ setShowMerge(false);
1101
+ setShowExtMsg(false);
1102
+ setShowSnooze(false);
1103
+ },
1104
+ autoCloseScheduledAt,
1105
+ onCancelScheduledClose: handleCancelScheduledClose,
1106
+ cancelingClose,
1107
+ reminderSuccess,
1090
1108
  showNextTicket,
1091
1109
  nextTicketId,
1092
1110
  nextTicketInfo,
@@ -1136,7 +1154,19 @@ const TicketConversation = () => {
1136
1154
  handleExtFileChange
1137
1155
  }
1138
1156
  ),
1139
- features.snooze && showSnooze && /* @__PURE__ */ jsx(SnoozePanel, { snoozeSaving, handleSnooze })
1157
+ features.snooze && showSnooze && /* @__PURE__ */ jsx(SnoozePanel, { snoozeSaving, handleSnooze }),
1158
+ features.autoClose && showReminder && /* @__PURE__ */ jsx(
1159
+ ReminderPanel,
1160
+ {
1161
+ clientFirstName: client?.firstName || "",
1162
+ clientEmail: client?.email || "",
1163
+ reminderHours,
1164
+ setReminderHours,
1165
+ reminderSending,
1166
+ reminderError,
1167
+ handleSendReminder
1168
+ }
1169
+ )
1140
1170
  ] }),
1141
1171
  /* @__PURE__ */ jsxs("div", { style: layoutStyles.sideColumn, children: [
1142
1172
  /* @__PURE__ */ jsx(