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