@consilioweb/payload-support 0.9.11 → 0.9.13

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.
@@ -50,6 +50,9 @@ const BillingClient = () => {
50
50
  const [projects, setProjects] = useState([]);
51
51
  const [projectsLoaded, setProjectsLoaded] = useState(false);
52
52
  const [copied, setCopied] = useState(false);
53
+ const [hideEmpty, setHideEmpty] = useState(false);
54
+ const [expandedSummaries, setExpandedSummaries] = useState(/* @__PURE__ */ new Set());
55
+ const [regeneratingIds, setRegeneratingIds] = useState(/* @__PURE__ */ new Set());
53
56
  const [billedTickets, setBilledTickets] = useState(() => {
54
57
  if (typeof window === "undefined") return /* @__PURE__ */ new Set();
55
58
  try {
@@ -68,16 +71,29 @@ const BillingClient = () => {
68
71
  return next;
69
72
  });
70
73
  }, []);
71
- const allTicketIds = data?.groups.flatMap((g) => g.tickets.map((t2) => t2.id)) || [];
74
+ const toggleSummary = useCallback((ticketId) => {
75
+ setExpandedSummaries((prev) => {
76
+ const next = new Set(prev);
77
+ if (next.has(ticketId)) next.delete(ticketId);
78
+ else next.add(ticketId);
79
+ return next;
80
+ });
81
+ }, []);
82
+ const visibleGroups = React.useMemo(() => {
83
+ if (!data) return [];
84
+ if (!hideEmpty) return data.groups;
85
+ return data.groups.map((g) => ({ ...g, tickets: g.tickets.filter((t2) => !t2.hasNoTimeEntries) })).filter((g) => g.tickets.length > 0);
86
+ }, [data, hideEmpty]);
87
+ const allTicketIds = visibleGroups.flatMap((g) => g.tickets.map((t2) => t2.id));
72
88
  const allBilled = allTicketIds.length > 0 && allTicketIds.every((id) => billedTickets.has(id));
73
89
  const toggleAll = useCallback(() => {
74
90
  setBilledTickets((prev) => {
75
- const ids = data?.groups.flatMap((g) => g.tickets.map((t2) => t2.id)) || [];
91
+ const ids = visibleGroups.flatMap((g) => g.tickets.map((t2) => t2.id));
76
92
  const next = ids.every((id) => prev.has(id)) ? /* @__PURE__ */ new Set() : new Set(ids);
77
93
  localStorage.setItem("billing-checked-tickets", JSON.stringify([...next]));
78
94
  return next;
79
95
  });
80
- }, [data]);
96
+ }, [visibleGroups]);
81
97
  const loadProjects = useCallback(async () => {
82
98
  if (projectsLoaded) return;
83
99
  try {
@@ -111,24 +127,65 @@ const BillingClient = () => {
111
127
  setFrom(range.from);
112
128
  setTo(range.to);
113
129
  };
130
+ const requestSynthesis = useCallback(async (ticketId, force) => {
131
+ setRegeneratingIds((prev) => new Set(prev).add(ticketId));
132
+ try {
133
+ const params = new URLSearchParams({ ticketId: String(ticketId) });
134
+ if (force) params.set("force", "true");
135
+ const res = await fetch(`/api/support/ticket-synthesis?${params}`, { method: "POST" });
136
+ if (res.ok) {
137
+ const json = await res.json();
138
+ setData((prev) => {
139
+ if (!prev) return prev;
140
+ return {
141
+ ...prev,
142
+ groups: prev.groups.map((g) => ({
143
+ ...g,
144
+ tickets: g.tickets.map(
145
+ (t2) => t2.id === ticketId ? { ...t2, aiSummary: json.summary, aiSummaryGeneratedAt: json.generatedAt, aiSummaryStatus: "done" } : t2
146
+ )
147
+ }))
148
+ };
149
+ });
150
+ setExpandedSummaries((prev) => new Set(prev).add(ticketId));
151
+ }
152
+ } catch (err) {
153
+ console.error("[billing] Synthesis error:", err);
154
+ } finally {
155
+ setRegeneratingIds((prev) => {
156
+ const next = new Set(prev);
157
+ next.delete(ticketId);
158
+ return next;
159
+ });
160
+ }
161
+ }, []);
114
162
  const copyRecap = useCallback(() => {
115
163
  if (!data) return;
116
164
  const lines = [];
117
165
  lines.push(`PRE-FACTURATION \u2014 Du ${from} au ${to}`);
118
166
  lines.push(`Taux horaire : ${rate} EUR/h`);
119
167
  lines.push("=".repeat(50));
120
- for (const group of data.groups) {
168
+ for (const group of visibleGroups) {
121
169
  lines.push("");
122
170
  lines.push(`PROJET : ${group.project?.name || "Sans projet"}`);
123
171
  if (group.client?.company) lines.push(`Client : ${group.client.company}`);
124
172
  lines.push("-".repeat(40));
125
173
  for (const ticket of group.tickets) {
126
- lines.push(` ${ticket.ticketNumber} \u2014 ${ticket.subject}`);
174
+ const flag = ticket.hasNoTimeEntries ? " [AUCUN TEMPS SAISI]" : "";
175
+ lines.push(` ${ticket.ticketNumber} \u2014 ${ticket.subject}${flag}`);
127
176
  for (const entry of ticket.entries) {
128
177
  lines.push(` ${entry.date} | ${formatDuration(entry.duration)} | ${entry.description || "-"}`);
129
178
  }
130
- const ticketAmount = ticket.billedAmount || Number(formatAmount(ticket.totalMinutes, rate));
131
- lines.push(` Sous-total : ${formatDuration(ticket.totalMinutes)} = ${ticketAmount.toFixed(2)} EUR${ticket.billedAmount ? " (forfait)" : ""}`);
179
+ if (ticket.entries.length > 0) {
180
+ const ticketAmount = ticket.billedAmount || Number(formatAmount(ticket.totalMinutes, rate));
181
+ lines.push(` Sous-total : ${formatDuration(ticket.totalMinutes)} = ${ticketAmount.toFixed(2)} EUR${ticket.billedAmount ? " (forfait)" : ""}`);
182
+ }
183
+ if (ticket.aiSummary) {
184
+ lines.push(" Detail des actions :");
185
+ for (const detailLine of ticket.aiSummary.split("\n")) {
186
+ lines.push(` ${detailLine}`);
187
+ }
188
+ }
132
189
  }
133
190
  const groupAmount = group.totalBilledAmount > 0 ? group.totalBilledAmount : Number(formatAmount(group.totalMinutes, rate));
134
191
  lines.push(` Total projet : ${formatDuration(group.totalMinutes)} = ${groupAmount.toFixed(2)} EUR`);
@@ -139,8 +196,13 @@ const BillingClient = () => {
139
196
  navigator.clipboard.writeText(lines.join("\n"));
140
197
  setCopied(true);
141
198
  setTimeout(() => setCopied(false), 2e3);
142
- }, [data, from, to, rate]);
143
- const totalTickets = data?.groups.reduce((sum, g) => sum + g.tickets.length, 0) || 0;
199
+ }, [data, visibleGroups, from, to, rate]);
200
+ const copyTicketSummary = useCallback((ticket) => {
201
+ const lines = [`${ticket.ticketNumber} \u2014 ${ticket.subject}`];
202
+ if (ticket.aiSummary) lines.push("", ticket.aiSummary);
203
+ navigator.clipboard.writeText(lines.join("\n"));
204
+ }, []);
205
+ const totalTickets = visibleGroups.reduce((sum, g) => sum + g.tickets.length, 0);
144
206
  return /* @__PURE__ */ jsxs("div", { className: styles.page, children: [
145
207
  /* @__PURE__ */ jsx("div", { className: styles.header, children: /* @__PURE__ */ jsxs("div", { children: [
146
208
  /* @__PURE__ */ jsx("h1", { className: styles.title, children: t("billing.title") }),
@@ -164,18 +226,10 @@ const BillingClient = () => {
164
226
  ] }),
165
227
  /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
166
228
  /* @__PURE__ */ jsx("label", { className: styles.label, children: t("billing.filters.project") }),
167
- /* @__PURE__ */ jsxs(
168
- "select",
169
- {
170
- value: projectId,
171
- onChange: (e) => setProjectId(e.target.value),
172
- className: styles.select,
173
- children: [
174
- /* @__PURE__ */ jsx("option", { value: "", children: t("ticket.allProjects") }),
175
- projects.map((p) => /* @__PURE__ */ jsx("option", { value: p.id, children: p.name }, p.id))
176
- ]
177
- }
178
- )
229
+ /* @__PURE__ */ jsxs("select", { value: projectId, onChange: (e) => setProjectId(e.target.value), className: styles.select, children: [
230
+ /* @__PURE__ */ jsx("option", { value: "", children: t("ticket.allProjects") }),
231
+ projects.map((p) => /* @__PURE__ */ jsx("option", { value: p.id, children: p.name }, p.id))
232
+ ] })
179
233
  ] }),
180
234
  /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
181
235
  /* @__PURE__ */ jsx("label", { className: styles.label, children: t("billing.filters.hourlyRate") }),
@@ -193,167 +247,230 @@ const BillingClient = () => {
193
247
  /* @__PURE__ */ jsx("span", { className: styles.rateUnit, children: t("billing.filters.rateUnit") })
194
248
  ] })
195
249
  ] }),
196
- /* @__PURE__ */ jsx(
197
- "button",
198
- {
199
- className: styles.btnPrimary,
200
- onClick: fetchBilling,
201
- disabled: loading,
202
- children: loading ? t("billing.filters.loading") : t("billing.filters.load")
203
- }
204
- )
250
+ /* @__PURE__ */ jsx("button", { className: styles.btnPrimary, onClick: fetchBilling, disabled: loading, children: loading ? t("billing.filters.loading") : t("billing.filters.load") })
205
251
  ] })
206
252
  ] }),
207
- data && /* @__PURE__ */ jsx(Fragment, { children: data.groups.length === 0 ? /* @__PURE__ */ jsx("div", { className: styles.empty, children: t("billing.empty") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
208
- data.groups.map((group, gi) => /* @__PURE__ */ jsxs("div", { className: styles.groupCard, children: [
209
- /* @__PURE__ */ jsxs("div", { className: styles.groupHeader, children: [
210
- /* @__PURE__ */ jsxs("div", { children: [
211
- /* @__PURE__ */ jsx("span", { className: styles.groupName, children: group.project?.name || "Sans projet" }),
212
- group.client?.company && /* @__PURE__ */ jsxs("span", { className: styles.groupClient, children: [
213
- "\u2014 ",
214
- group.client.company
215
- ] })
216
- ] }),
217
- /* @__PURE__ */ jsxs("div", { className: styles.groupTotals, children: [
218
- /* @__PURE__ */ jsx("div", { className: styles.groupDuration, children: formatDuration(group.totalMinutes) }),
219
- group.totalBilledAmount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
220
- /* @__PURE__ */ jsxs("div", { className: styles.groupAmountBilled, children: [
221
- group.totalBilledAmount.toFixed(2),
222
- " EUR facture"
223
- ] }),
224
- /* @__PURE__ */ jsxs("div", { className: styles.groupAmountStrike, children: [
253
+ data && /* @__PURE__ */ jsxs(Fragment, { children: [
254
+ data.ticketsWithoutTime > 0 && /* @__PURE__ */ jsxs("div", { className: styles.warningBanner, children: [
255
+ /* @__PURE__ */ jsxs("span", { children: [
256
+ /* @__PURE__ */ jsx("strong", { children: data.ticketsWithoutTime }),
257
+ " ticket",
258
+ data.ticketsWithoutTime > 1 ? "s" : "",
259
+ " actif",
260
+ data.ticketsWithoutTime > 1 ? "s" : "",
261
+ " sans temps saisi sur la periode."
262
+ ] }),
263
+ /* @__PURE__ */ jsxs("label", { className: styles.toggleLabel, children: [
264
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: hideEmpty, onChange: (e) => setHideEmpty(e.target.checked) }),
265
+ /* @__PURE__ */ jsx("span", { children: "Masquer ces tickets" })
266
+ ] })
267
+ ] }),
268
+ visibleGroups.length === 0 ? /* @__PURE__ */ jsx("div", { className: styles.empty, children: t("billing.empty") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
269
+ visibleGroups.map((group, gi) => /* @__PURE__ */ jsxs("div", { className: styles.groupCard, children: [
270
+ /* @__PURE__ */ jsxs("div", { className: styles.groupHeader, children: [
271
+ /* @__PURE__ */ jsxs("div", { children: [
272
+ /* @__PURE__ */ jsx("span", { className: styles.groupName, children: group.project?.name || "Sans projet" }),
273
+ group.client?.company && /* @__PURE__ */ jsxs("span", { className: styles.groupClient, children: [
274
+ "\u2014 ",
275
+ group.client.company
276
+ ] })
277
+ ] }),
278
+ /* @__PURE__ */ jsxs("div", { className: styles.groupTotals, children: [
279
+ /* @__PURE__ */ jsx("div", { className: styles.groupDuration, children: formatDuration(group.totalMinutes) }),
280
+ group.totalBilledAmount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
281
+ /* @__PURE__ */ jsxs("div", { className: styles.groupAmountBilled, children: [
282
+ group.totalBilledAmount.toFixed(2),
283
+ " EUR facture"
284
+ ] }),
285
+ /* @__PURE__ */ jsxs("div", { className: styles.groupAmountStrike, children: [
286
+ formatAmount(group.totalMinutes, rate),
287
+ " EUR (temps)"
288
+ ] })
289
+ ] }) : /* @__PURE__ */ jsxs("div", { className: styles.groupAmount, children: [
225
290
  formatAmount(group.totalMinutes, rate),
226
- " EUR (temps)"
291
+ " EUR"
227
292
  ] })
228
- ] }) : /* @__PURE__ */ jsxs("div", { className: styles.groupAmount, children: [
229
- formatAmount(group.totalMinutes, rate),
230
- " EUR"
231
293
  ] })
232
- ] })
233
- ] }),
234
- /* @__PURE__ */ jsxs("table", { className: styles.table, children: [
235
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
236
- /* @__PURE__ */ jsx("th", { className: styles.thCheckbox, children: /* @__PURE__ */ jsx(
237
- "input",
238
- {
239
- type: "checkbox",
240
- checked: group.tickets.every((t2) => billedTickets.has(t2.id)),
241
- onChange: () => {
242
- const ids = group.tickets.map((t2) => t2.id);
243
- const allChecked = ids.every((id) => billedTickets.has(id));
244
- setBilledTickets((prev) => {
245
- const next = new Set(prev);
246
- ids.forEach((id) => allChecked ? next.delete(id) : next.add(id));
247
- localStorage.setItem("billing-checked-tickets", JSON.stringify([...next]));
248
- return next;
249
- });
250
- },
251
- className: styles.checkbox,
252
- title: "Tout cocher/decocher"
253
- }
254
- ) }),
255
- /* @__PURE__ */ jsx("th", { className: styles.th, children: "N\xB0 Ticket" }),
256
- /* @__PURE__ */ jsx("th", { className: styles.thLeft, children: "Sujet" }),
257
- /* @__PURE__ */ jsx("th", { className: styles.th, children: "Date" }),
258
- /* @__PURE__ */ jsx("th", { className: styles.th, children: "Duree" }),
259
- /* @__PURE__ */ jsx("th", { className: styles.thLeft, children: "Description" }),
260
- /* @__PURE__ */ jsx("th", { className: styles.th, children: "Montant" }),
261
- /* @__PURE__ */ jsx("th", { className: styles.th, children: "Facture" })
262
- ] }) }),
263
- /* @__PURE__ */ jsx("tbody", { children: group.tickets.map((ticket) => {
264
- const isBilled = billedTickets.has(ticket.id);
265
- return ticket.entries.map((entry, ei) => /* @__PURE__ */ jsxs(
266
- "tr",
267
- {
268
- className: `${isBilled ? styles.tableRowBilled : styles.tableRow} ${!isBilled && ei % 2 === 0 ? styles.tableRowEven : ""} ${!isBilled && ei % 2 !== 0 ? styles.tableRowOdd : ""}`,
269
- children: [
270
- ei === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
271
- /* @__PURE__ */ jsx("td", { className: styles.td, rowSpan: ticket.entries.length, style: { verticalAlign: "middle" }, children: /* @__PURE__ */ jsx(
272
- "input",
273
- {
274
- type: "checkbox",
275
- checked: isBilled,
276
- onChange: () => toggleBilled(ticket.id),
277
- className: styles.checkbox,
278
- title: isBilled ? "Marquer comme non facture" : "Marquer comme facture"
279
- }
280
- ) }),
281
- /* @__PURE__ */ jsx("td", { className: `${styles.td} ${styles.bold} ${isBilled ? styles.strikethrough : ""}`, rowSpan: ticket.entries.length, children: /* @__PURE__ */ jsx(
282
- "a",
283
- {
284
- href: `/admin/support/ticket?id=${ticket.id}`,
285
- className: styles.ticketLink,
286
- children: ticket.ticketNumber
287
- }
288
- ) }),
289
- /* @__PURE__ */ jsx("td", { className: `${styles.tdLeft} ${isBilled ? styles.strikethrough : ""}`, rowSpan: ticket.entries.length, children: ticket.subject })
290
- ] }) : null,
291
- /* @__PURE__ */ jsx("td", { className: styles.td, children: entry.date }),
292
- /* @__PURE__ */ jsx("td", { className: styles.td, children: formatDuration(entry.duration) }),
293
- /* @__PURE__ */ jsx("td", { className: `${styles.tdLeft} ${styles.secondary}`, children: entry.description || "-" }),
294
- /* @__PURE__ */ jsxs("td", { className: `${styles.td} ${styles.bold}`, children: [
295
- formatAmount(entry.duration, rate),
296
- " EUR"
297
- ] }),
298
- ei === 0 ? /* @__PURE__ */ jsx(
299
- "td",
294
+ ] }),
295
+ /* @__PURE__ */ jsxs("table", { className: styles.table, children: [
296
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
297
+ /* @__PURE__ */ jsx("th", { className: styles.thCheckbox, children: /* @__PURE__ */ jsx(
298
+ "input",
299
+ {
300
+ type: "checkbox",
301
+ checked: group.tickets.every((t2) => billedTickets.has(t2.id)),
302
+ onChange: () => {
303
+ const ids = group.tickets.map((t2) => t2.id);
304
+ const allChecked = ids.every((id) => billedTickets.has(id));
305
+ setBilledTickets((prev) => {
306
+ const next = new Set(prev);
307
+ ids.forEach((id) => allChecked ? next.delete(id) : next.add(id));
308
+ localStorage.setItem("billing-checked-tickets", JSON.stringify([...next]));
309
+ return next;
310
+ });
311
+ },
312
+ className: styles.checkbox,
313
+ title: "Tout cocher/decocher"
314
+ }
315
+ ) }),
316
+ /* @__PURE__ */ jsx("th", { className: styles.th, children: "N\xB0 Ticket" }),
317
+ /* @__PURE__ */ jsx("th", { className: styles.thLeft, children: "Sujet" }),
318
+ /* @__PURE__ */ jsx("th", { className: styles.th, children: "Date" }),
319
+ /* @__PURE__ */ jsx("th", { className: styles.th, children: "Duree" }),
320
+ /* @__PURE__ */ jsx("th", { className: styles.thLeft, children: "Description" }),
321
+ /* @__PURE__ */ jsx("th", { className: styles.th, children: "Montant" }),
322
+ /* @__PURE__ */ jsx("th", { className: styles.th, children: "Facture" })
323
+ ] }) }),
324
+ /* @__PURE__ */ jsx("tbody", { children: group.tickets.map((ticket) => {
325
+ const isBilled = billedTickets.has(ticket.id);
326
+ const isExpanded = expandedSummaries.has(ticket.id);
327
+ const isRegenerating = regeneratingIds.has(ticket.id);
328
+ const rowSpan = Math.max(ticket.entries.length, 1);
329
+ const renderTicketHeaderCells = () => /* @__PURE__ */ jsxs(Fragment, { children: [
330
+ /* @__PURE__ */ jsx("td", { className: styles.td, rowSpan, style: { verticalAlign: "middle" }, children: /* @__PURE__ */ jsx(
331
+ "input",
332
+ {
333
+ type: "checkbox",
334
+ checked: isBilled,
335
+ onChange: () => toggleBilled(ticket.id),
336
+ className: styles.checkbox,
337
+ title: isBilled ? "Marquer comme non facture" : "Marquer comme facture"
338
+ }
339
+ ) }),
340
+ /* @__PURE__ */ jsxs("td", { className: `${styles.td} ${styles.bold} ${isBilled ? styles.strikethrough : ""}`, rowSpan, children: [
341
+ /* @__PURE__ */ jsx("a", { href: `/admin/support/ticket?id=${ticket.id}`, className: styles.ticketLink, children: ticket.ticketNumber }),
342
+ /* @__PURE__ */ jsxs(
343
+ "button",
300
344
  {
301
- className: `${styles.td} ${ticket.billedAmount ? styles.billedAmount : styles.secondary}`,
302
- rowSpan: ticket.entries.length,
303
- children: ticket.billedAmount ? `${ticket.billedAmount.toFixed(2)} EUR` : "-"
345
+ className: styles.summaryBtn,
346
+ onClick: () => toggleSummary(ticket.id),
347
+ title: isExpanded ? "Masquer le detail" : "Afficher le detail IA",
348
+ children: [
349
+ isExpanded ? "\u25BC" : "\u25B6",
350
+ " IA"
351
+ ]
304
352
  }
305
- ) : null
306
- ]
307
- },
308
- `${ticket.id}-${ei}`
309
- ));
310
- }) })
311
- ] })
312
- ] }, gi)),
313
- /* @__PURE__ */ jsxs("div", { className: styles.grandTotal, children: [
314
- /* @__PURE__ */ jsxs("div", { children: [
315
- /* @__PURE__ */ jsxs("div", { className: styles.totalMeta, children: [
316
- totalTickets,
317
- " ticket",
318
- totalTickets > 1 ? "s" : "",
319
- " facturable",
320
- totalTickets > 1 ? "s" : "",
321
- billedTickets.size > 0 && /* @__PURE__ */ jsxs("span", { className: styles.totalChecked, children: [
322
- "(",
323
- allTicketIds.filter((id) => billedTickets.has(id)).length,
324
- " coche",
325
- allTicketIds.filter((id) => billedTickets.has(id)).length > 1 ? "s" : "",
326
- ")"
353
+ )
354
+ ] }),
355
+ /* @__PURE__ */ jsxs("td", { className: `${styles.tdLeft} ${isBilled ? styles.strikethrough : ""}`, rowSpan, children: [
356
+ ticket.subject,
357
+ ticket.hasNoTimeEntries && /* @__PURE__ */ jsx("span", { className: styles.noTimeBadge, title: "Aucun temps saisi sur la periode", children: "\u26A0 Aucun temps saisi" })
358
+ ] })
359
+ ] });
360
+ const rows = [];
361
+ if (ticket.entries.length === 0) {
362
+ rows.push(
363
+ /* @__PURE__ */ jsxs(
364
+ "tr",
365
+ {
366
+ className: `${isBilled ? styles.tableRowBilled : styles.tableRowNoTime}`,
367
+ children: [
368
+ renderTicketHeaderCells(),
369
+ /* @__PURE__ */ jsx("td", { className: `${styles.td} ${styles.secondary}`, colSpan: 5, children: /* @__PURE__ */ jsx("em", { children: "Pas de saisie de temps. Verifier si du temps a ete oublie." }) })
370
+ ]
371
+ },
372
+ `${ticket.id}-empty`
373
+ )
374
+ );
375
+ } else {
376
+ ticket.entries.forEach((entry, ei) => {
377
+ rows.push(
378
+ /* @__PURE__ */ jsxs(
379
+ "tr",
380
+ {
381
+ className: `${isBilled ? styles.tableRowBilled : styles.tableRow} ${!isBilled && ei % 2 === 0 ? styles.tableRowEven : ""} ${!isBilled && ei % 2 !== 0 ? styles.tableRowOdd : ""}`,
382
+ children: [
383
+ ei === 0 ? renderTicketHeaderCells() : null,
384
+ /* @__PURE__ */ jsx("td", { className: styles.td, children: entry.date }),
385
+ /* @__PURE__ */ jsx("td", { className: styles.td, children: formatDuration(entry.duration) }),
386
+ /* @__PURE__ */ jsx("td", { className: `${styles.tdLeft} ${styles.secondary}`, children: entry.description || "-" }),
387
+ /* @__PURE__ */ jsxs("td", { className: `${styles.td} ${styles.bold}`, children: [
388
+ formatAmount(entry.duration, rate),
389
+ " EUR"
390
+ ] }),
391
+ ei === 0 ? /* @__PURE__ */ jsx(
392
+ "td",
393
+ {
394
+ className: `${styles.td} ${ticket.billedAmount ? styles.billedAmount : styles.secondary}`,
395
+ rowSpan: ticket.entries.length,
396
+ children: ticket.billedAmount ? `${ticket.billedAmount.toFixed(2)} EUR` : "-"
397
+ }
398
+ ) : null
399
+ ]
400
+ },
401
+ `${ticket.id}-${ei}`
402
+ )
403
+ );
404
+ });
405
+ }
406
+ if (isExpanded) {
407
+ rows.push(
408
+ /* @__PURE__ */ jsx("tr", { className: styles.summaryRow, children: /* @__PURE__ */ jsxs("td", { colSpan: 8, className: styles.summaryCell, children: [
409
+ /* @__PURE__ */ jsxs("div", { className: styles.summaryHeader, children: [
410
+ /* @__PURE__ */ jsx("strong", { children: "Synthese IA des actions" }),
411
+ /* @__PURE__ */ jsxs("div", { className: styles.summaryActions, children: [
412
+ ticket.aiSummaryGeneratedAt && /* @__PURE__ */ jsxs("span", { className: styles.summaryMeta, children: [
413
+ "Genere le ",
414
+ new Date(ticket.aiSummaryGeneratedAt).toLocaleString("fr-FR")
415
+ ] }),
416
+ ticket.aiSummary && /* @__PURE__ */ jsx(
417
+ "button",
418
+ {
419
+ className: styles.summaryAction,
420
+ onClick: () => copyTicketSummary(ticket),
421
+ children: "Copier"
422
+ }
423
+ ),
424
+ /* @__PURE__ */ jsx(
425
+ "button",
426
+ {
427
+ className: styles.summaryAction,
428
+ onClick: () => requestSynthesis(ticket.id, !!ticket.aiSummary),
429
+ disabled: isRegenerating,
430
+ children: isRegenerating ? "Generation..." : ticket.aiSummary ? "Regenerer" : "Generer"
431
+ }
432
+ )
433
+ ] })
434
+ ] }),
435
+ ticket.aiSummaryStatus === "pending" && !ticket.aiSummary ? /* @__PURE__ */ jsx("div", { className: styles.summaryEmpty, children: 'Generation en cours en arriere-plan. Cliquer sur "Generer" pour forcer.' }) : ticket.aiSummary ? /* @__PURE__ */ jsx("pre", { className: styles.summaryText, children: ticket.aiSummary }) : /* @__PURE__ */ jsx("div", { className: styles.summaryEmpty, children: 'Pas de synthese disponible. La synthese est generee automatiquement quand le ticket passe en "resolu", ou manuellement via le bouton "Generer".' })
436
+ ] }) }, `${ticket.id}-summary`)
437
+ );
438
+ }
439
+ return rows;
440
+ }) })
441
+ ] })
442
+ ] }, gi)),
443
+ /* @__PURE__ */ jsxs("div", { className: styles.grandTotal, children: [
444
+ /* @__PURE__ */ jsxs("div", { children: [
445
+ /* @__PURE__ */ jsxs("div", { className: styles.totalMeta, children: [
446
+ totalTickets,
447
+ " ticket",
448
+ totalTickets > 1 ? "s" : "",
449
+ " affiche",
450
+ totalTickets > 1 ? "s" : "",
451
+ billedTickets.size > 0 && /* @__PURE__ */ jsxs("span", { className: styles.totalChecked, children: [
452
+ "(",
453
+ allTicketIds.filter((id) => billedTickets.has(id)).length,
454
+ " coche",
455
+ allTicketIds.filter((id) => billedTickets.has(id)).length > 1 ? "s" : "",
456
+ ")"
457
+ ] })
458
+ ] }),
459
+ /* @__PURE__ */ jsxs("div", { className: styles.totalAmount, children: [
460
+ "Total : ",
461
+ formatDuration(data.grandTotalMinutes),
462
+ " =",
463
+ " ",
464
+ data.grandTotalBilledAmount > 0 ? `${data.grandTotalBilledAmount.toFixed(2)} EUR` : `${formatAmount(data.grandTotalMinutes, rate)} EUR`
327
465
  ] })
328
466
  ] }),
329
- /* @__PURE__ */ jsxs("div", { className: styles.totalAmount, children: [
330
- "Total : ",
331
- formatDuration(data.grandTotalMinutes),
332
- " =",
333
- " ",
334
- data.grandTotalBilledAmount > 0 ? `${data.grandTotalBilledAmount.toFixed(2)} EUR` : `${formatAmount(data.grandTotalMinutes, rate)} EUR`
467
+ /* @__PURE__ */ jsxs("div", { className: styles.totalActions, children: [
468
+ /* @__PURE__ */ jsx("button", { className: allBilled ? styles.btnSecondary : styles.btnGreen, onClick: toggleAll, children: allBilled ? t("billing.totals.uncheckAll") : t("billing.totals.checkAll") }),
469
+ /* @__PURE__ */ jsx("button", { className: copied ? styles.btnSuccess : styles.btnAmber, onClick: copyRecap, children: copied ? t("billing.totals.copiedRecap") : t("billing.totals.copyRecap") })
335
470
  ] })
336
- ] }),
337
- /* @__PURE__ */ jsxs("div", { className: styles.totalActions, children: [
338
- /* @__PURE__ */ jsx(
339
- "button",
340
- {
341
- className: allBilled ? styles.btnSecondary : styles.btnGreen,
342
- onClick: toggleAll,
343
- children: allBilled ? t("billing.totals.uncheckAll") : t("billing.totals.checkAll")
344
- }
345
- ),
346
- /* @__PURE__ */ jsx(
347
- "button",
348
- {
349
- className: copied ? styles.btnSuccess : styles.btnAmber,
350
- onClick: copyRecap,
351
- children: copied ? t("billing.totals.copiedRecap") : t("billing.totals.copyRecap")
352
- }
353
- )
354
471
  ] })
355
472
  ] })
356
- ] }) })
473
+ ] })
357
474
  ] });
358
475
  };
359
476
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@consilioweb/payload-support",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
4
4
  "description": "Payload CMS plugin — professional support & ticketing system with AI, SLA, time tracking, live chat, and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",