@consilioweb/payload-support 0.8.2 → 0.9.4

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 (50) hide show
  1. package/dist/components/RichTextEditor/index.cjs +233 -0
  2. package/dist/components/RichTextEditor/index.js +232 -0
  3. package/dist/components/TicketConversation/components/CodeBlock.cjs +24 -7
  4. package/dist/components/TicketConversation/components/CodeBlock.js +23 -9
  5. package/dist/index.cjs +19 -1
  6. package/dist/index.js +19 -1
  7. package/dist/views/BillingView/client.cjs +260 -103
  8. package/dist/views/BillingView/client.js +259 -103
  9. package/dist/views/ChatView/client.cjs +184 -137
  10. package/dist/views/ChatView/client.js +180 -137
  11. package/dist/views/CrmView/client.cjs +270 -122
  12. package/dist/views/CrmView/client.js +266 -122
  13. package/dist/views/EmailTrackingView/client.cjs +80 -69
  14. package/dist/views/EmailTrackingView/client.js +80 -70
  15. package/dist/views/ImportConversationView/client.cjs +127 -94
  16. package/dist/views/ImportConversationView/client.js +123 -94
  17. package/dist/views/LogsView/client.cjs +56 -58
  18. package/dist/views/LogsView/client.js +52 -58
  19. package/dist/views/NewTicketView/client.cjs +39 -55
  20. package/dist/views/NewTicketView/client.js +38 -55
  21. package/dist/views/PendingEmailsView/client.cjs +399 -102
  22. package/dist/views/PendingEmailsView/client.js +396 -103
  23. package/dist/views/SupportDashboardView/client.cjs +276 -137
  24. package/dist/views/SupportDashboardView/client.js +275 -137
  25. package/dist/views/TicketDetailView/client.cjs +487 -204
  26. package/dist/views/TicketDetailView/client.js +486 -204
  27. package/dist/views/TicketInboxView/client.cjs +62 -65
  28. package/dist/views/TicketInboxView/client.js +62 -66
  29. package/dist/views/TicketingSettingsView/client.cjs +10 -8
  30. package/dist/views/TicketingSettingsView/client.js +10 -8
  31. package/dist/views/TimeDashboardView/client.cjs +70 -59
  32. package/dist/views/TimeDashboardView/client.js +69 -59
  33. package/package.json +6 -2
  34. package/src/components/RichTextEditor/index.tsx +261 -0
  35. package/src/components/TicketConversation/components/CodeBlock.tsx +53 -14
  36. package/src/plugin.ts +2 -0
  37. package/src/utils/emailTemplate.ts +37 -0
  38. package/src/views/BillingView/client.tsx +362 -69
  39. package/src/views/ChatView/client.tsx +225 -140
  40. package/src/views/CrmView/client.tsx +447 -189
  41. package/src/views/EmailTrackingView/client.tsx +111 -71
  42. package/src/views/ImportConversationView/client.tsx +255 -70
  43. package/src/views/LogsView/client.tsx +85 -50
  44. package/src/views/NewTicketView/client.tsx +37 -53
  45. package/src/views/PendingEmailsView/client.tsx +512 -92
  46. package/src/views/SupportDashboardView/client.tsx +294 -134
  47. package/src/views/TicketDetailView/client.tsx +486 -213
  48. package/src/views/TicketInboxView/client.tsx +52 -61
  49. package/src/views/TicketingSettingsView/client.tsx +10 -9
  50. package/src/views/TimeDashboardView/client.tsx +184 -69
@@ -2,6 +2,7 @@
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import React, { useState, useCallback } from 'react';
4
4
  import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
5
+ import styles from '../../styles/BillingView.module.scss';
5
6
 
6
7
  function formatDuration(minutes) {
7
8
  const h = Math.floor(minutes / 60);
@@ -11,20 +12,32 @@ function formatDuration(minutes) {
11
12
  return `${h}h ${m}min`;
12
13
  }
13
14
  function formatAmount(minutes, rate) {
14
- return (minutes / 60 * rate).toFixed(2);
15
+ const hours = minutes / 60;
16
+ return (hours * rate).toFixed(2);
15
17
  }
16
18
  function getMonthRange(offset) {
17
19
  const now = /* @__PURE__ */ new Date();
18
- const start = new Date(now.getFullYear(), now.getMonth() + offset, 1);
19
- const end = new Date(now.getFullYear(), now.getMonth() + offset + 1, 0);
20
- return { from: start.toISOString().split("T")[0], to: end.toISOString().split("T")[0] };
20
+ const year = now.getFullYear();
21
+ const month = now.getMonth() + offset;
22
+ const start = new Date(year, month, 1);
23
+ const end = new Date(year, month + 1, 0);
24
+ return {
25
+ from: start.toISOString().split("T")[0],
26
+ to: end.toISOString().split("T")[0]
27
+ };
21
28
  }
22
29
  function getQuarterRange(offset) {
23
30
  const now = /* @__PURE__ */ new Date();
24
- const q = Math.floor(now.getMonth() / 3) + offset;
25
- const start = new Date(now.getFullYear(), q * 3, 1);
26
- const end = new Date(now.getFullYear(), q * 3 + 3, 0);
27
- return { from: start.toISOString().split("T")[0], to: end.toISOString().split("T")[0] };
31
+ const currentQuarter = Math.floor(now.getMonth() / 3);
32
+ const quarter = currentQuarter + offset;
33
+ const year = now.getFullYear();
34
+ const startMonth = quarter * 3;
35
+ const start = new Date(year, startMonth, 1);
36
+ const end = new Date(year, startMonth + 3, 0);
37
+ return {
38
+ from: start.toISOString().split("T")[0],
39
+ to: end.toISOString().split("T")[0]
40
+ };
28
41
  }
29
42
  const BillingClient = () => {
30
43
  const { t } = useTranslation();
@@ -37,6 +50,34 @@ const BillingClient = () => {
37
50
  const [projects, setProjects] = useState([]);
38
51
  const [projectsLoaded, setProjectsLoaded] = useState(false);
39
52
  const [copied, setCopied] = useState(false);
53
+ const [billedTickets, setBilledTickets] = useState(() => {
54
+ if (typeof window === "undefined") return /* @__PURE__ */ new Set();
55
+ try {
56
+ const saved = localStorage.getItem("billing-checked-tickets");
57
+ return saved ? new Set(JSON.parse(saved)) : /* @__PURE__ */ new Set();
58
+ } catch {
59
+ return /* @__PURE__ */ new Set();
60
+ }
61
+ });
62
+ const toggleBilled = useCallback((ticketId) => {
63
+ setBilledTickets((prev) => {
64
+ const next = new Set(prev);
65
+ if (next.has(ticketId)) next.delete(ticketId);
66
+ else next.add(ticketId);
67
+ localStorage.setItem("billing-checked-tickets", JSON.stringify([...next]));
68
+ return next;
69
+ });
70
+ }, []);
71
+ const allTicketIds = data?.groups.flatMap((g) => g.tickets.map((t2) => t2.id)) || [];
72
+ const allBilled = allTicketIds.length > 0 && allTicketIds.every((id) => billedTickets.has(id));
73
+ const toggleAll = useCallback(() => {
74
+ setBilledTickets((prev) => {
75
+ const ids = data?.groups.flatMap((g) => g.tickets.map((t2) => t2.id)) || [];
76
+ const next = ids.every((id) => prev.has(id)) ? /* @__PURE__ */ new Set() : new Set(ids);
77
+ localStorage.setItem("billing-checked-tickets", JSON.stringify([...next]));
78
+ return next;
79
+ });
80
+ }, [data]);
40
81
  const loadProjects = useCallback(async () => {
41
82
  if (projectsLoaded) return;
42
83
  try {
@@ -45,8 +86,7 @@ const BillingClient = () => {
45
86
  const json = await res.json();
46
87
  setProjects(json.docs?.map((p) => ({ id: p.id, name: p.name })) || []);
47
88
  }
48
- } catch (err) {
49
- console.warn("[support] loadProjects error:", err);
89
+ } catch {
50
90
  }
51
91
  setProjectsLoaded(true);
52
92
  }, [projectsLoaded]);
@@ -59,9 +99,11 @@ const BillingClient = () => {
59
99
  const params = new URLSearchParams({ from, to });
60
100
  if (projectId) params.set("projectId", projectId);
61
101
  const res = await fetch(`/api/support/billing?${params}`);
62
- if (res.ok) setData(await res.json());
102
+ if (res.ok) {
103
+ setData(await res.json());
104
+ }
63
105
  } catch (err) {
64
- console.warn("[support] fetchBilling error:", err);
106
+ console.error("[billing] Fetch error:", err);
65
107
  }
66
108
  setLoading(false);
67
109
  }, [from, to, projectId]);
@@ -71,131 +113,245 @@ const BillingClient = () => {
71
113
  };
72
114
  const copyRecap = useCallback(() => {
73
115
  if (!data) return;
74
- const lines = [`PRE-FACTURATION -- Du ${from} au ${to}`, `Taux horaire : ${rate} EUR/h`, "=".repeat(50)];
116
+ const lines = [];
117
+ lines.push(`PRE-FACTURATION \u2014 Du ${from} au ${to}`);
118
+ lines.push(`Taux horaire : ${rate} EUR/h`);
119
+ lines.push("=".repeat(50));
75
120
  for (const group of data.groups) {
76
- lines.push("", `PROJET : ${group.project?.name || "Sans projet"}`);
121
+ lines.push("");
122
+ lines.push(`PROJET : ${group.project?.name || "Sans projet"}`);
77
123
  if (group.client?.company) lines.push(`Client : ${group.client.company}`);
124
+ lines.push("-".repeat(40));
78
125
  for (const ticket of group.tickets) {
79
- lines.push(` ${ticket.ticketNumber} -- ${ticket.subject}`);
80
- for (const entry of ticket.entries) lines.push(` ${entry.date} | ${formatDuration(entry.duration)} | ${entry.description || "-"}`);
81
- lines.push(` Sous-total : ${formatDuration(ticket.totalMinutes)} = ${formatAmount(ticket.totalMinutes, rate)} EUR`);
126
+ lines.push(` ${ticket.ticketNumber} \u2014 ${ticket.subject}`);
127
+ for (const entry of ticket.entries) {
128
+ lines.push(` ${entry.date} | ${formatDuration(entry.duration)} | ${entry.description || "-"}`);
129
+ }
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)" : ""}`);
82
132
  }
133
+ const groupAmount = group.totalBilledAmount > 0 ? group.totalBilledAmount : Number(formatAmount(group.totalMinutes, rate));
134
+ lines.push(` Total projet : ${formatDuration(group.totalMinutes)} = ${groupAmount.toFixed(2)} EUR`);
83
135
  }
84
- lines.push("", "=".repeat(50), `TOTAL : ${formatDuration(data.grandTotalMinutes)} = ${formatAmount(data.grandTotalMinutes, rate)} EUR`);
136
+ lines.push("");
137
+ lines.push("=".repeat(50));
138
+ lines.push(`TOTAL GENERAL : ${formatDuration(data.grandTotalMinutes)} = ${formatAmount(data.grandTotalMinutes, rate)} EUR`);
85
139
  navigator.clipboard.writeText(lines.join("\n"));
86
140
  setCopied(true);
87
141
  setTimeout(() => setCopied(false), 2e3);
88
142
  }, [data, from, to, rate]);
89
- const S = {
90
- page: { padding: "20px 30px", maxWidth: 1100, margin: "0 auto" },
91
- filters: { marginBottom: 20 },
92
- quickPeriod: { display: "flex", gap: 6, marginBottom: 8 },
93
- btn: { padding: "6px 12px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 12, cursor: "pointer", background: "var(--theme-elevation-0)", color: "var(--theme-text)" },
94
- btnPrimary: { padding: "6px 12px", borderRadius: 6, border: "none", fontSize: 12, cursor: "pointer", background: "#2563eb", color: "#fff", fontWeight: 600 },
95
- filterRow: { display: "flex", gap: 12, alignItems: "flex-end", flexWrap: "wrap" },
96
- fieldGroup: { display: "flex", flexDirection: "column", gap: 4 },
97
- label: { fontSize: 11, fontWeight: 600, color: "var(--theme-elevation-500)" },
98
- input: { padding: "6px 10px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 12, color: "var(--theme-text)", background: "var(--theme-elevation-0)" },
99
- select: { padding: "6px 10px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 12, color: "var(--theme-text)", background: "var(--theme-elevation-0)" },
100
- groupCard: { marginBottom: 16, borderRadius: 10, border: "1px solid var(--theme-elevation-150)", overflow: "hidden" },
101
- groupHeader: { display: "flex", justifyContent: "space-between", padding: "12px 16px", background: "var(--theme-elevation-50)", borderBottom: "1px solid var(--theme-elevation-150)" },
102
- table: { width: "100%", borderCollapse: "collapse", fontSize: 12 },
103
- th: { textAlign: "left", padding: "6px 8px", borderBottom: "1px solid var(--theme-elevation-200)", fontSize: 11, color: "var(--theme-elevation-500)" },
104
- td: { padding: "6px 8px", borderBottom: "1px solid var(--theme-elevation-100)" },
105
- grandTotal: { padding: 16, borderRadius: 10, border: "2px solid #2563eb", display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 16 }
106
- };
107
- return /* @__PURE__ */ jsxs("div", { style: S.page, children: [
108
- /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
109
- /* @__PURE__ */ jsx("h1", { style: { fontSize: 22, fontWeight: 700, margin: 0 }, children: t("billing.title") }),
110
- /* @__PURE__ */ jsx("p", { style: { fontSize: 13, color: "var(--theme-elevation-500)", margin: "4px 0 0" }, children: t("billing.subtitle") })
111
- ] }),
112
- /* @__PURE__ */ jsxs("div", { style: S.filters, children: [
113
- /* @__PURE__ */ jsxs("div", { style: S.quickPeriod, children: [
114
- /* @__PURE__ */ jsx("button", { style: S.btnPrimary, onClick: () => setPeriod(getMonthRange(0)), children: t("billing.filters.thisMonth") }),
115
- /* @__PURE__ */ jsx("button", { style: S.btn, onClick: () => setPeriod(getMonthRange(-1)), children: t("billing.filters.lastMonth") }),
116
- /* @__PURE__ */ jsx("button", { style: S.btn, onClick: () => setPeriod(getQuarterRange(0)), children: t("billing.filters.thisQuarter") })
143
+ const totalTickets = data?.groups.reduce((sum, g) => sum + g.tickets.length, 0) || 0;
144
+ return /* @__PURE__ */ jsxs("div", { className: styles.page, children: [
145
+ /* @__PURE__ */ jsx("div", { className: styles.header, children: /* @__PURE__ */ jsxs("div", { children: [
146
+ /* @__PURE__ */ jsx("h1", { className: styles.title, children: t("billing.title") }),
147
+ /* @__PURE__ */ jsx("p", { className: styles.subtitle, children: t("billing.subtitle") })
148
+ ] }) }),
149
+ /* @__PURE__ */ jsxs("div", { className: styles.filters, children: [
150
+ /* @__PURE__ */ jsxs("div", { className: styles.quickPeriod, children: [
151
+ /* @__PURE__ */ jsx("button", { className: styles.btnPrimary, onClick: () => setPeriod(getMonthRange(0)), children: t("billing.filters.thisMonth") }),
152
+ /* @__PURE__ */ jsx("button", { className: styles.btnSecondary, onClick: () => setPeriod(getMonthRange(-1)), children: t("billing.filters.lastMonth") }),
153
+ /* @__PURE__ */ jsx("button", { className: styles.btnAmber, onClick: () => setPeriod(getQuarterRange(0)), children: t("billing.filters.thisQuarter") }),
154
+ /* @__PURE__ */ jsx("button", { className: styles.btnMuted, onClick: () => setPeriod(getQuarterRange(-1)), children: t("billing.filters.lastQuarter") })
117
155
  ] }),
118
- /* @__PURE__ */ jsxs("div", { style: S.filterRow, children: [
119
- /* @__PURE__ */ jsxs("div", { style: S.fieldGroup, children: [
120
- /* @__PURE__ */ jsx("label", { style: S.label, children: t("billing.filters.from") }),
121
- /* @__PURE__ */ jsx("input", { type: "date", value: from, onChange: (e) => setFrom(e.target.value), style: S.input })
156
+ /* @__PURE__ */ jsxs("div", { className: styles.filterRow, children: [
157
+ /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
158
+ /* @__PURE__ */ jsx("label", { className: styles.label, children: t("billing.filters.from") }),
159
+ /* @__PURE__ */ jsx("input", { type: "date", value: from, onChange: (e) => setFrom(e.target.value), className: styles.input })
122
160
  ] }),
123
- /* @__PURE__ */ jsxs("div", { style: S.fieldGroup, children: [
124
- /* @__PURE__ */ jsx("label", { style: S.label, children: t("billing.filters.to") }),
125
- /* @__PURE__ */ jsx("input", { type: "date", value: to, onChange: (e) => setTo(e.target.value), style: S.input })
161
+ /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
162
+ /* @__PURE__ */ jsx("label", { className: styles.label, children: t("billing.filters.to") }),
163
+ /* @__PURE__ */ jsx("input", { type: "date", value: to, onChange: (e) => setTo(e.target.value), className: styles.input })
126
164
  ] }),
127
- /* @__PURE__ */ jsxs("div", { style: S.fieldGroup, children: [
128
- /* @__PURE__ */ jsx("label", { style: S.label, children: t("billing.filters.project") }),
129
- /* @__PURE__ */ jsxs("select", { value: projectId, onChange: (e) => setProjectId(e.target.value), style: S.select, children: [
130
- /* @__PURE__ */ jsx("option", { value: "", children: t("common.all") }),
131
- projects.map((p) => /* @__PURE__ */ jsx("option", { value: p.id, children: p.name }, p.id))
132
- ] })
165
+ /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
166
+ /* @__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
+ )
133
179
  ] }),
134
- /* @__PURE__ */ jsxs("div", { style: S.fieldGroup, children: [
135
- /* @__PURE__ */ jsx("label", { style: S.label, children: t("billing.filters.hourlyRate") }),
136
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [
137
- /* @__PURE__ */ jsx("input", { type: "number", value: rate, onChange: (e) => setRate(Number(e.target.value)), style: { ...S.input, width: 70 }, min: 0 }),
138
- /* @__PURE__ */ jsx("span", { style: { fontSize: 11 }, children: t("billing.filters.rateUnit") })
180
+ /* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
181
+ /* @__PURE__ */ jsx("label", { className: styles.label, children: t("billing.filters.hourlyRate") }),
182
+ /* @__PURE__ */ jsxs("div", { className: styles.rateRow, children: [
183
+ /* @__PURE__ */ jsx(
184
+ "input",
185
+ {
186
+ type: "number",
187
+ value: rate,
188
+ onChange: (e) => setRate(Number(e.target.value)),
189
+ className: styles.rateInput,
190
+ min: 0
191
+ }
192
+ ),
193
+ /* @__PURE__ */ jsx("span", { className: styles.rateUnit, children: t("billing.filters.rateUnit") })
139
194
  ] })
140
195
  ] }),
141
- /* @__PURE__ */ jsx("button", { style: S.btnPrimary, onClick: fetchBilling, disabled: loading, children: loading ? t("billing.filters.loading") : t("billing.filters.load") })
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
+ )
142
205
  ] })
143
206
  ] }),
144
- data && /* @__PURE__ */ jsx(Fragment, { children: data.groups.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: 40, textAlign: "center", color: "#94a3b8" }, children: t("billing.empty") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
145
- data.groups.map((group, gi) => /* @__PURE__ */ jsxs("div", { style: S.groupCard, children: [
146
- /* @__PURE__ */ jsxs("div", { style: S.groupHeader, children: [
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: [
147
210
  /* @__PURE__ */ jsxs("div", { children: [
148
- /* @__PURE__ */ jsx("span", { style: { fontWeight: 700 }, children: group.project?.name || "Sans projet" }),
149
- group.client?.company && /* @__PURE__ */ jsxs("span", { style: { color: "var(--theme-elevation-500)" }, children: [
150
- " -- ",
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 ",
151
214
  group.client.company
152
215
  ] })
153
216
  ] }),
154
- /* @__PURE__ */ jsxs("div", { style: { textAlign: "right" }, children: [
155
- /* @__PURE__ */ jsx("div", { style: { fontWeight: 700 }, children: formatDuration(group.totalMinutes) }),
156
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: "#2563eb", fontWeight: 600 }, children: [
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: [
225
+ formatAmount(group.totalMinutes, rate),
226
+ " EUR (temps)"
227
+ ] })
228
+ ] }) : /* @__PURE__ */ jsxs("div", { className: styles.groupAmount, children: [
157
229
  formatAmount(group.totalMinutes, rate),
158
230
  " EUR"
159
231
  ] })
160
232
  ] })
161
233
  ] }),
162
- /* @__PURE__ */ jsxs("table", { style: S.table, children: [
234
+ /* @__PURE__ */ jsxs("table", { className: styles.table, children: [
163
235
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
164
- /* @__PURE__ */ jsx("th", { style: S.th, children: t("billing.table.ticketNumber") }),
165
- /* @__PURE__ */ jsx("th", { style: S.th, children: t("billing.table.subject") }),
166
- /* @__PURE__ */ jsx("th", { style: S.th, children: t("billing.table.date") }),
167
- /* @__PURE__ */ jsx("th", { style: S.th, children: t("billing.table.duration") }),
168
- /* @__PURE__ */ jsx("th", { style: S.th, children: t("billing.table.description") }),
169
- /* @__PURE__ */ jsx("th", { style: { ...S.th, textAlign: "right" }, children: t("billing.table.amount") })
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" })
170
262
  ] }) }),
171
- /* @__PURE__ */ jsx("tbody", { children: group.tickets.map((ticket) => ticket.entries.map((entry, ei) => /* @__PURE__ */ jsxs("tr", { children: [
172
- ei === 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
173
- /* @__PURE__ */ jsx("td", { style: { ...S.td, fontWeight: 600 }, rowSpan: ticket.entries.length, children: /* @__PURE__ */ jsx("a", { href: `/admin/support/ticket?id=${ticket.id}`, style: { color: "#2563eb", textDecoration: "none" }, children: ticket.ticketNumber }) }),
174
- /* @__PURE__ */ jsx("td", { style: S.td, rowSpan: ticket.entries.length, children: ticket.subject })
175
- ] }),
176
- /* @__PURE__ */ jsx("td", { style: S.td, children: entry.date }),
177
- /* @__PURE__ */ jsx("td", { style: S.td, children: formatDuration(entry.duration) }),
178
- /* @__PURE__ */ jsx("td", { style: S.td, children: entry.description || "-" }),
179
- /* @__PURE__ */ jsxs("td", { style: { ...S.td, textAlign: "right", fontWeight: 600 }, children: [
180
- formatAmount(entry.duration, rate),
181
- " EUR"
182
- ] })
183
- ] }, `${ticket.id}-${ei}`))) })
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",
300
+ {
301
+ className: `${styles.td} ${ticket.billedAmount ? styles.billedAmount : styles.secondary}`,
302
+ rowSpan: ticket.entries.length,
303
+ children: ticket.billedAmount ? `${ticket.billedAmount.toFixed(2)} EUR` : "-"
304
+ }
305
+ ) : null
306
+ ]
307
+ },
308
+ `${ticket.id}-${ei}`
309
+ ));
310
+ }) })
184
311
  ] })
185
312
  ] }, gi)),
186
- /* @__PURE__ */ jsxs("div", { style: S.grandTotal, children: [
313
+ /* @__PURE__ */ jsxs("div", { className: styles.grandTotal, children: [
187
314
  /* @__PURE__ */ jsxs("div", { children: [
188
- /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "var(--theme-elevation-500)" }, children: t("billing.totals.billableTicketsPlural", { count: String(data.groups.reduce((s2, g) => s2 + g.tickets.length, 0)) }) }),
189
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 18, fontWeight: 700 }, children: [
190
- t("billing.totals.total"),
191
- " : ",
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
+ ")"
327
+ ] })
328
+ ] }),
329
+ /* @__PURE__ */ jsxs("div", { className: styles.totalAmount, children: [
330
+ "Total : ",
192
331
  formatDuration(data.grandTotalMinutes),
193
- " = ",
194
- formatAmount(data.grandTotalMinutes, rate),
195
- " EUR"
332
+ " =",
333
+ " ",
334
+ data.grandTotalBilledAmount > 0 ? `${data.grandTotalBilledAmount.toFixed(2)} EUR` : `${formatAmount(data.grandTotalMinutes, rate)} EUR`
196
335
  ] })
197
336
  ] }),
198
- /* @__PURE__ */ jsx("button", { style: S.btnPrimary, onClick: copyRecap, children: copied ? t("billing.totals.copiedRecap") : t("billing.totals.copyRecap") })
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
+ ] })
199
355
  ] })
200
356
  ] }) })
201
357
  ] });