@consilioweb/payload-support 0.6.1 → 0.6.2

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 (70) hide show
  1. package/dist/views/BillingView/client.cjs +209 -0
  2. package/dist/views/BillingView/client.js +204 -0
  3. package/dist/views/BillingView/index.cjs +34 -0
  4. package/dist/views/BillingView/index.js +29 -0
  5. package/dist/views/ChatView/client.cjs +253 -0
  6. package/dist/views/ChatView/client.js +252 -0
  7. package/dist/views/ChatView/index.cjs +34 -0
  8. package/dist/views/ChatView/index.js +29 -0
  9. package/dist/views/CrmView/client.cjs +233 -0
  10. package/dist/views/CrmView/client.js +232 -0
  11. package/dist/views/CrmView/index.cjs +34 -0
  12. package/dist/views/CrmView/index.js +29 -0
  13. package/dist/views/EmailTrackingView/client.cjs +159 -0
  14. package/dist/views/EmailTrackingView/client.js +154 -0
  15. package/dist/views/EmailTrackingView/index.cjs +34 -0
  16. package/dist/views/EmailTrackingView/index.js +29 -0
  17. package/dist/views/ImportConversationView/client.cjs +205 -0
  18. package/dist/views/ImportConversationView/client.js +204 -0
  19. package/dist/views/ImportConversationView/index.cjs +34 -0
  20. package/dist/views/ImportConversationView/index.js +29 -0
  21. package/dist/views/LogsView/client.cjs +149 -0
  22. package/dist/views/LogsView/client.js +148 -0
  23. package/dist/views/LogsView/index.cjs +32 -0
  24. package/dist/views/LogsView/index.js +27 -0
  25. package/dist/views/NewTicketView/client.cjs +229 -0
  26. package/dist/views/NewTicketView/client.js +224 -0
  27. package/dist/views/NewTicketView/index.cjs +32 -0
  28. package/dist/views/NewTicketView/index.js +27 -0
  29. package/dist/views/PendingEmailsView/client.cjs +173 -0
  30. package/dist/views/PendingEmailsView/client.js +172 -0
  31. package/dist/views/PendingEmailsView/index.cjs +34 -0
  32. package/dist/views/PendingEmailsView/index.js +29 -0
  33. package/dist/views/SupportDashboardView/client.cjs +301 -0
  34. package/dist/views/SupportDashboardView/client.js +296 -0
  35. package/dist/views/SupportDashboardView/index.cjs +34 -0
  36. package/dist/views/SupportDashboardView/index.js +29 -0
  37. package/dist/views/TicketDetailView/client.cjs +850 -0
  38. package/dist/views/TicketDetailView/client.js +844 -0
  39. package/dist/views/TicketDetailView/index.cjs +34 -0
  40. package/dist/views/TicketDetailView/index.js +29 -0
  41. package/dist/views/TicketInboxView/client.cjs +299 -0
  42. package/dist/views/TicketInboxView/client.js +294 -0
  43. package/dist/views/TicketInboxView/index.cjs +32 -0
  44. package/dist/views/TicketInboxView/index.js +27 -0
  45. package/dist/{views.css → views/TicketingSettingsView/TicketingSettings.module.scss} +138 -66
  46. package/dist/views/TicketingSettingsView/client.cjs +733 -0
  47. package/dist/views/TicketingSettingsView/client.js +728 -0
  48. package/dist/views/TicketingSettingsView/index.cjs +34 -0
  49. package/dist/views/TicketingSettingsView/index.js +29 -0
  50. package/dist/views/TimeDashboardView/client.cjs +169 -0
  51. package/dist/views/TimeDashboardView/client.js +164 -0
  52. package/dist/views/TimeDashboardView/index.cjs +34 -0
  53. package/dist/views/TimeDashboardView/index.js +29 -0
  54. package/dist/views/shared/AdminViewHeader.cjs +68 -0
  55. package/dist/views/shared/AdminViewHeader.js +67 -0
  56. package/dist/views/shared/ErrorBoundary.cjs +55 -0
  57. package/dist/views/shared/ErrorBoundary.js +50 -0
  58. package/dist/views/shared/Skeleton.cjs +77 -0
  59. package/dist/views/shared/Skeleton.js +72 -0
  60. package/dist/views/shared/adminTokens.cjs +34 -0
  61. package/dist/views/shared/adminTokens.js +31 -0
  62. package/dist/views/shared/config.cjs +44 -0
  63. package/dist/views/shared/config.js +40 -0
  64. package/dist/views/shared/index.cjs +58 -0
  65. package/dist/views/shared/index.js +5 -0
  66. package/dist/views.cjs +77 -6175
  67. package/dist/views.d.cts +13 -30
  68. package/dist/views.d.ts +13 -30
  69. package/dist/views.js +13 -6165
  70. package/package.json +1 -1
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var templates = require('@payloadcms/next/templates');
7
+ var navigation = require('next/navigation');
8
+ var ErrorBoundary = require('../shared/ErrorBoundary');
9
+ var client = require('./client');
10
+
11
+ const TicketingSettingsView = ({ initPageResult }) => {
12
+ const { req, visibleEntities } = initPageResult;
13
+ if (!req.user) {
14
+ navigation.redirect("/admin/login");
15
+ }
16
+ return /* @__PURE__ */ jsxRuntime.jsx(
17
+ templates.DefaultTemplate,
18
+ {
19
+ i18n: req.i18n,
20
+ locale: initPageResult.locale,
21
+ params: {},
22
+ payload: req.payload,
23
+ permissions: initPageResult.permissions,
24
+ searchParams: {},
25
+ user: req.user,
26
+ visibleEntities,
27
+ children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary.AdminErrorBoundary, { viewName: "TicketingSettingsView", children: /* @__PURE__ */ jsxRuntime.jsx(client.TicketingSettingsClient, {}) })
28
+ }
29
+ );
30
+ };
31
+ var TicketingSettingsView_default = TicketingSettingsView;
32
+
33
+ exports.TicketingSettingsView = TicketingSettingsView;
34
+ exports.default = TicketingSettingsView_default;
@@ -0,0 +1,29 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { DefaultTemplate } from '@payloadcms/next/templates';
3
+ import { redirect } from 'next/navigation';
4
+ import { AdminErrorBoundary } from '../shared/ErrorBoundary';
5
+ import { TicketingSettingsClient } from './client';
6
+
7
+ const TicketingSettingsView = ({ initPageResult }) => {
8
+ const { req, visibleEntities } = initPageResult;
9
+ if (!req.user) {
10
+ redirect("/admin/login");
11
+ }
12
+ return /* @__PURE__ */ jsx(
13
+ DefaultTemplate,
14
+ {
15
+ i18n: req.i18n,
16
+ locale: initPageResult.locale,
17
+ params: {},
18
+ payload: req.payload,
19
+ permissions: initPageResult.permissions,
20
+ searchParams: {},
21
+ user: req.user,
22
+ visibleEntities,
23
+ children: /* @__PURE__ */ jsx(AdminErrorBoundary, { viewName: "TicketingSettingsView", children: /* @__PURE__ */ jsx(TicketingSettingsClient, {}) })
24
+ }
25
+ );
26
+ };
27
+ var TicketingSettingsView_default = TicketingSettingsView;
28
+
29
+ export { TicketingSettingsView, TicketingSettingsView_default as default };
@@ -0,0 +1,169 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var React = require('react');
5
+ var useTranslation = require('../../components/TicketConversation/hooks/useTranslation');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var React__default = /*#__PURE__*/_interopDefault(React);
10
+
11
+ function formatDuration(minutes) {
12
+ const h = Math.floor(minutes / 60);
13
+ const m = minutes % 60;
14
+ if (h === 0) return `${m}min`;
15
+ if (m === 0) return `${h}h`;
16
+ return `${h}h${m}m`;
17
+ }
18
+ function getWeekNumber(d) {
19
+ const oneJan = new Date(d.getFullYear(), 0, 1);
20
+ return Math.ceil(((d.getTime() - oneJan.getTime()) / 864e5 + oneJan.getDay() + 1) / 7);
21
+ }
22
+ function getMonthRange(offset) {
23
+ const now = /* @__PURE__ */ new Date();
24
+ const start = new Date(now.getFullYear(), now.getMonth() + offset, 1);
25
+ const end = new Date(now.getFullYear(), now.getMonth() + offset + 1, 0);
26
+ return { from: start.toISOString().split("T")[0], to: end.toISOString().split("T")[0] };
27
+ }
28
+ const MONTHS_FR = ["Jan", "Fev", "Mar", "Avr", "Mai", "Jun", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec"];
29
+ const TimeDashboardClient = () => {
30
+ const { t } = useTranslation.useTranslation();
31
+ const [entries, setEntries] = React.useState([]);
32
+ const [loading, setLoading] = React.useState(true);
33
+ const [from, setFrom] = React.useState(() => getMonthRange(0).from);
34
+ const [to, setTo] = React.useState(() => getMonthRange(0).to);
35
+ const [groupBy, setGroupBy] = React.useState("day");
36
+ const fetchEntries = React.useCallback(async () => {
37
+ setLoading(true);
38
+ try {
39
+ const params = new URLSearchParams({ limit: "500", depth: "2", sort: "-date" });
40
+ if (from) params.set("where[date][greater_than_equal]", from);
41
+ if (to) params.set("where[date][less_than_equal]", to);
42
+ const res = await fetch(`/api/time-entries?${params}`);
43
+ if (res.ok) {
44
+ const json = await res.json();
45
+ setEntries(json.docs || []);
46
+ }
47
+ } catch {
48
+ }
49
+ setLoading(false);
50
+ }, [from, to]);
51
+ React.useEffect(() => {
52
+ fetchEntries();
53
+ }, [fetchEntries]);
54
+ const totalMinutes = entries.reduce((sum, e) => sum + (e.duration || 0), 0);
55
+ const grouped = React__default.default.useMemo(() => {
56
+ const map = /* @__PURE__ */ new Map();
57
+ for (const entry of entries) {
58
+ let key;
59
+ if (groupBy === "day") {
60
+ key = entry.date ? entry.date.split("T")[0] : "Sans date";
61
+ } else if (groupBy === "week") {
62
+ const d = new Date(entry.date);
63
+ key = `Semaine ${getWeekNumber(d)} (${MONTHS_FR[d.getMonth()]} ${d.getFullYear()})`;
64
+ } else {
65
+ const ticket = typeof entry.ticket === "object" ? entry.ticket : null;
66
+ const project = ticket && typeof ticket.project === "object" ? ticket.project : null;
67
+ key = project?.name || "Sans projet";
68
+ }
69
+ if (!map.has(key)) map.set(key, []);
70
+ map.get(key).push(entry);
71
+ }
72
+ return Array.from(map.entries()).map(([label, items]) => ({ label, entries: items, totalMinutes: items.reduce((sum, e) => sum + (e.duration || 0), 0) }));
73
+ }, [entries, groupBy]);
74
+ const dailyChart = React__default.default.useMemo(() => {
75
+ const dayMap = /* @__PURE__ */ new Map();
76
+ for (const entry of entries) {
77
+ const day = entry.date ? entry.date.split("T")[0] : null;
78
+ if (day) dayMap.set(day, (dayMap.get(day) || 0) + (entry.duration || 0));
79
+ }
80
+ return Array.from(dayMap.entries()).sort(([a], [b]) => a.localeCompare(b)).slice(-30).map(([day, mins]) => ({ day, minutes: mins }));
81
+ }, [entries]);
82
+ const maxDailyMinutes = Math.max(...dailyChart.map((d) => d.minutes), 1);
83
+ const setPeriod = (range) => {
84
+ setFrom(range.from);
85
+ setTo(range.to);
86
+ };
87
+ const S = {
88
+ page: { padding: "20px 30px", maxWidth: 1100, margin: "0 auto" },
89
+ 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)" },
90
+ btnPrimary: { padding: "6px 12px", borderRadius: 6, border: "none", fontSize: 12, cursor: "pointer", background: "#2563eb", color: "#fff", fontWeight: 600 },
91
+ kpis: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12, marginBottom: 20 },
92
+ kpiCard: { padding: "16px 20px", borderRadius: 10, border: "1px solid var(--theme-elevation-150)" },
93
+ groupCard: { marginBottom: 12, borderRadius: 10, border: "1px solid var(--theme-elevation-150)", overflow: "hidden" },
94
+ groupHeader: { display: "flex", justifyContent: "space-between", padding: "10px 16px", background: "var(--theme-elevation-50)", borderBottom: "1px solid var(--theme-elevation-150)" },
95
+ table: { width: "100%", borderCollapse: "collapse", fontSize: 12 },
96
+ td: { padding: "6px 8px", borderBottom: "1px solid var(--theme-elevation-100)" }
97
+ };
98
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.page, children: [
99
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }, children: [
100
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
101
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { style: { fontSize: 22, fontWeight: 700, margin: 0 }, children: t("timeDashboard.title") }),
102
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: 13, color: "var(--theme-elevation-500)", margin: "4px 0 0" }, children: t("timeDashboard.subtitle") })
103
+ ] }),
104
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: "/admin/collections/time-entries/create", style: { ...S.btnPrimary, textDecoration: "none", display: "inline-block" }, children: t("timeDashboard.newEntry") })
105
+ ] }),
106
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 16 }, children: [
107
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, marginBottom: 8 }, children: [
108
+ /* @__PURE__ */ jsxRuntime.jsx("button", { style: S.btnPrimary, onClick: () => setPeriod(getMonthRange(0)), children: t("timeDashboard.filters.thisMonth") }),
109
+ /* @__PURE__ */ jsxRuntime.jsx("button", { style: S.btn, onClick: () => setPeriod(getMonthRange(-1)), children: t("timeDashboard.filters.lastMonth") }),
110
+ /* @__PURE__ */ jsxRuntime.jsx("button", { style: S.btn, onClick: () => setPeriod(getMonthRange(-2)), children: t("timeDashboard.filters.twoMonthsAgo") })
111
+ ] }),
112
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 12, alignItems: "flex-end" }, children: [
113
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
114
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: { fontSize: 11, fontWeight: 600, display: "block", marginBottom: 4, color: "var(--theme-elevation-500)" }, children: t("timeDashboard.filters.from") }),
115
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "date", value: from, onChange: (e) => setFrom(e.target.value), style: { padding: "6px 10px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 12, color: "var(--theme-text)", background: "var(--theme-elevation-0)" } })
116
+ ] }),
117
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
118
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: { fontSize: 11, fontWeight: 600, display: "block", marginBottom: 4, color: "var(--theme-elevation-500)" }, children: t("timeDashboard.filters.to") }),
119
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "date", value: to, onChange: (e) => setTo(e.target.value), style: { padding: "6px 10px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 12, color: "var(--theme-text)", background: "var(--theme-elevation-0)" } })
120
+ ] }),
121
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
122
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: { fontSize: 11, fontWeight: 600, display: "block", marginBottom: 4, color: "var(--theme-elevation-500)" }, children: t("timeDashboard.filters.groupBy") }),
123
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { value: groupBy, onChange: (e) => setGroupBy(e.target.value), style: { padding: "6px 10px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 12, color: "var(--theme-text)", background: "var(--theme-elevation-0)" }, children: [
124
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "day", children: t("timeDashboard.filters.day") }),
125
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "week", children: t("timeDashboard.filters.week") }),
126
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "project", children: t("timeDashboard.filters.project") })
127
+ ] })
128
+ ] })
129
+ ] })
130
+ ] }),
131
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 40, textAlign: "center", color: "#94a3b8" }, children: t("common.loading") }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
132
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.kpis, children: [
133
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.kpiCard, children: [
134
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "var(--theme-elevation-500)" }, children: t("timeDashboard.kpis.totalTime") }),
135
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 24, fontWeight: 700, color: "#2563eb" }, children: formatDuration(totalMinutes) })
136
+ ] }),
137
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.kpiCard, children: [
138
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "var(--theme-elevation-500)" }, children: t("timeDashboard.kpis.entries") }),
139
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 24, fontWeight: 700, color: "#d97706" }, children: entries.length })
140
+ ] }),
141
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.kpiCard, children: [
142
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "var(--theme-elevation-500)" }, children: t("timeDashboard.kpis.dailyAverage") }),
143
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 24, fontWeight: 700, color: "#ea580c" }, children: dailyChart.length > 0 ? formatDuration(Math.round(totalMinutes / dailyChart.length)) : "-" })
144
+ ] })
145
+ ] }),
146
+ dailyChart.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: 16, borderRadius: 10, border: "1px solid var(--theme-elevation-150)", marginBottom: 20 }, children: [
147
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 13, fontWeight: 700, marginBottom: 8 }, children: t("timeDashboard.chart.title") }),
148
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "flex-end", gap: 3, height: 80 }, children: dailyChart.map((d) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, background: "#3b82f6", borderRadius: "3px 3px 0 0", height: `${Math.max(d.minutes / maxDailyMinutes * 100, 4)}%` }, title: `${d.day}: ${formatDuration(d.minutes)}` }, d.day)) })
149
+ ] }),
150
+ grouped.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 40, textAlign: "center", color: "#94a3b8" }, children: t("timeDashboard.empty") }) : grouped.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.groupCard, children: [
151
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.groupHeader, children: [
152
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 700 }, children: group.label }),
153
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 700, color: "#2563eb" }, children: formatDuration(group.totalMinutes) })
154
+ ] }),
155
+ /* @__PURE__ */ jsxRuntime.jsx("table", { style: S.table, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: group.entries.map((entry) => {
156
+ const ticket = typeof entry.ticket === "object" ? entry.ticket : null;
157
+ return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
158
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: S.td, children: ticket ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: `/admin/collections/tickets/${ticket.id}`, style: { color: "#2563eb", textDecoration: "none", fontWeight: 600 }, children: ticket.ticketNumber || `#${ticket.id}` }) : "-" }),
159
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: S.td, children: ticket?.subject || "" }),
160
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: { ...S.td, color: "var(--theme-elevation-500)" }, children: entry.description || "-" }),
161
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: { ...S.td, textAlign: "right", fontWeight: 600 }, children: formatDuration(entry.duration) })
162
+ ] }, entry.id);
163
+ }) }) })
164
+ ] }, group.label))
165
+ ] })
166
+ ] });
167
+ };
168
+
169
+ exports.TimeDashboardClient = TimeDashboardClient;
@@ -0,0 +1,164 @@
1
+ "use client";
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import React, { useState, useCallback, useEffect } from 'react';
4
+ import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation';
5
+
6
+ function formatDuration(minutes) {
7
+ const h = Math.floor(minutes / 60);
8
+ const m = minutes % 60;
9
+ if (h === 0) return `${m}min`;
10
+ if (m === 0) return `${h}h`;
11
+ return `${h}h${m}m`;
12
+ }
13
+ function getWeekNumber(d) {
14
+ const oneJan = new Date(d.getFullYear(), 0, 1);
15
+ return Math.ceil(((d.getTime() - oneJan.getTime()) / 864e5 + oneJan.getDay() + 1) / 7);
16
+ }
17
+ function getMonthRange(offset) {
18
+ const now = /* @__PURE__ */ new Date();
19
+ const start = new Date(now.getFullYear(), now.getMonth() + offset, 1);
20
+ const end = new Date(now.getFullYear(), now.getMonth() + offset + 1, 0);
21
+ return { from: start.toISOString().split("T")[0], to: end.toISOString().split("T")[0] };
22
+ }
23
+ const MONTHS_FR = ["Jan", "Fev", "Mar", "Avr", "Mai", "Jun", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec"];
24
+ const TimeDashboardClient = () => {
25
+ const { t } = useTranslation();
26
+ const [entries, setEntries] = useState([]);
27
+ const [loading, setLoading] = useState(true);
28
+ const [from, setFrom] = useState(() => getMonthRange(0).from);
29
+ const [to, setTo] = useState(() => getMonthRange(0).to);
30
+ const [groupBy, setGroupBy] = useState("day");
31
+ const fetchEntries = useCallback(async () => {
32
+ setLoading(true);
33
+ try {
34
+ const params = new URLSearchParams({ limit: "500", depth: "2", sort: "-date" });
35
+ if (from) params.set("where[date][greater_than_equal]", from);
36
+ if (to) params.set("where[date][less_than_equal]", to);
37
+ const res = await fetch(`/api/time-entries?${params}`);
38
+ if (res.ok) {
39
+ const json = await res.json();
40
+ setEntries(json.docs || []);
41
+ }
42
+ } catch {
43
+ }
44
+ setLoading(false);
45
+ }, [from, to]);
46
+ useEffect(() => {
47
+ fetchEntries();
48
+ }, [fetchEntries]);
49
+ const totalMinutes = entries.reduce((sum, e) => sum + (e.duration || 0), 0);
50
+ const grouped = React.useMemo(() => {
51
+ const map = /* @__PURE__ */ new Map();
52
+ for (const entry of entries) {
53
+ let key;
54
+ if (groupBy === "day") {
55
+ key = entry.date ? entry.date.split("T")[0] : "Sans date";
56
+ } else if (groupBy === "week") {
57
+ const d = new Date(entry.date);
58
+ key = `Semaine ${getWeekNumber(d)} (${MONTHS_FR[d.getMonth()]} ${d.getFullYear()})`;
59
+ } else {
60
+ const ticket = typeof entry.ticket === "object" ? entry.ticket : null;
61
+ const project = ticket && typeof ticket.project === "object" ? ticket.project : null;
62
+ key = project?.name || "Sans projet";
63
+ }
64
+ if (!map.has(key)) map.set(key, []);
65
+ map.get(key).push(entry);
66
+ }
67
+ return Array.from(map.entries()).map(([label, items]) => ({ label, entries: items, totalMinutes: items.reduce((sum, e) => sum + (e.duration || 0), 0) }));
68
+ }, [entries, groupBy]);
69
+ const dailyChart = React.useMemo(() => {
70
+ const dayMap = /* @__PURE__ */ new Map();
71
+ for (const entry of entries) {
72
+ const day = entry.date ? entry.date.split("T")[0] : null;
73
+ if (day) dayMap.set(day, (dayMap.get(day) || 0) + (entry.duration || 0));
74
+ }
75
+ return Array.from(dayMap.entries()).sort(([a], [b]) => a.localeCompare(b)).slice(-30).map(([day, mins]) => ({ day, minutes: mins }));
76
+ }, [entries]);
77
+ const maxDailyMinutes = Math.max(...dailyChart.map((d) => d.minutes), 1);
78
+ const setPeriod = (range) => {
79
+ setFrom(range.from);
80
+ setTo(range.to);
81
+ };
82
+ const S = {
83
+ page: { padding: "20px 30px", maxWidth: 1100, margin: "0 auto" },
84
+ 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)" },
85
+ btnPrimary: { padding: "6px 12px", borderRadius: 6, border: "none", fontSize: 12, cursor: "pointer", background: "#2563eb", color: "#fff", fontWeight: 600 },
86
+ kpis: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12, marginBottom: 20 },
87
+ kpiCard: { padding: "16px 20px", borderRadius: 10, border: "1px solid var(--theme-elevation-150)" },
88
+ groupCard: { marginBottom: 12, borderRadius: 10, border: "1px solid var(--theme-elevation-150)", overflow: "hidden" },
89
+ groupHeader: { display: "flex", justifyContent: "space-between", padding: "10px 16px", background: "var(--theme-elevation-50)", borderBottom: "1px solid var(--theme-elevation-150)" },
90
+ table: { width: "100%", borderCollapse: "collapse", fontSize: 12 },
91
+ td: { padding: "6px 8px", borderBottom: "1px solid var(--theme-elevation-100)" }
92
+ };
93
+ return /* @__PURE__ */ jsxs("div", { style: S.page, children: [
94
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }, children: [
95
+ /* @__PURE__ */ jsxs("div", { children: [
96
+ /* @__PURE__ */ jsx("h1", { style: { fontSize: 22, fontWeight: 700, margin: 0 }, children: t("timeDashboard.title") }),
97
+ /* @__PURE__ */ jsx("p", { style: { fontSize: 13, color: "var(--theme-elevation-500)", margin: "4px 0 0" }, children: t("timeDashboard.subtitle") })
98
+ ] }),
99
+ /* @__PURE__ */ jsx("a", { href: "/admin/collections/time-entries/create", style: { ...S.btnPrimary, textDecoration: "none", display: "inline-block" }, children: t("timeDashboard.newEntry") })
100
+ ] }),
101
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
102
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, marginBottom: 8 }, children: [
103
+ /* @__PURE__ */ jsx("button", { style: S.btnPrimary, onClick: () => setPeriod(getMonthRange(0)), children: t("timeDashboard.filters.thisMonth") }),
104
+ /* @__PURE__ */ jsx("button", { style: S.btn, onClick: () => setPeriod(getMonthRange(-1)), children: t("timeDashboard.filters.lastMonth") }),
105
+ /* @__PURE__ */ jsx("button", { style: S.btn, onClick: () => setPeriod(getMonthRange(-2)), children: t("timeDashboard.filters.twoMonthsAgo") })
106
+ ] }),
107
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 12, alignItems: "flex-end" }, children: [
108
+ /* @__PURE__ */ jsxs("div", { children: [
109
+ /* @__PURE__ */ jsx("label", { style: { fontSize: 11, fontWeight: 600, display: "block", marginBottom: 4, color: "var(--theme-elevation-500)" }, children: t("timeDashboard.filters.from") }),
110
+ /* @__PURE__ */ jsx("input", { type: "date", value: from, onChange: (e) => setFrom(e.target.value), style: { padding: "6px 10px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 12, color: "var(--theme-text)", background: "var(--theme-elevation-0)" } })
111
+ ] }),
112
+ /* @__PURE__ */ jsxs("div", { children: [
113
+ /* @__PURE__ */ jsx("label", { style: { fontSize: 11, fontWeight: 600, display: "block", marginBottom: 4, color: "var(--theme-elevation-500)" }, children: t("timeDashboard.filters.to") }),
114
+ /* @__PURE__ */ jsx("input", { type: "date", value: to, onChange: (e) => setTo(e.target.value), style: { padding: "6px 10px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 12, color: "var(--theme-text)", background: "var(--theme-elevation-0)" } })
115
+ ] }),
116
+ /* @__PURE__ */ jsxs("div", { children: [
117
+ /* @__PURE__ */ jsx("label", { style: { fontSize: 11, fontWeight: 600, display: "block", marginBottom: 4, color: "var(--theme-elevation-500)" }, children: t("timeDashboard.filters.groupBy") }),
118
+ /* @__PURE__ */ jsxs("select", { value: groupBy, onChange: (e) => setGroupBy(e.target.value), style: { padding: "6px 10px", borderRadius: 6, border: "1px solid var(--theme-elevation-200)", fontSize: 12, color: "var(--theme-text)", background: "var(--theme-elevation-0)" }, children: [
119
+ /* @__PURE__ */ jsx("option", { value: "day", children: t("timeDashboard.filters.day") }),
120
+ /* @__PURE__ */ jsx("option", { value: "week", children: t("timeDashboard.filters.week") }),
121
+ /* @__PURE__ */ jsx("option", { value: "project", children: t("timeDashboard.filters.project") })
122
+ ] })
123
+ ] })
124
+ ] })
125
+ ] }),
126
+ loading ? /* @__PURE__ */ jsx("div", { style: { padding: 40, textAlign: "center", color: "#94a3b8" }, children: t("common.loading") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
127
+ /* @__PURE__ */ jsxs("div", { style: S.kpis, children: [
128
+ /* @__PURE__ */ jsxs("div", { style: S.kpiCard, children: [
129
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "var(--theme-elevation-500)" }, children: t("timeDashboard.kpis.totalTime") }),
130
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 24, fontWeight: 700, color: "#2563eb" }, children: formatDuration(totalMinutes) })
131
+ ] }),
132
+ /* @__PURE__ */ jsxs("div", { style: S.kpiCard, children: [
133
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "var(--theme-elevation-500)" }, children: t("timeDashboard.kpis.entries") }),
134
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 24, fontWeight: 700, color: "#d97706" }, children: entries.length })
135
+ ] }),
136
+ /* @__PURE__ */ jsxs("div", { style: S.kpiCard, children: [
137
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "var(--theme-elevation-500)" }, children: t("timeDashboard.kpis.dailyAverage") }),
138
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 24, fontWeight: 700, color: "#ea580c" }, children: dailyChart.length > 0 ? formatDuration(Math.round(totalMinutes / dailyChart.length)) : "-" })
139
+ ] })
140
+ ] }),
141
+ dailyChart.length > 0 && /* @__PURE__ */ jsxs("div", { style: { padding: 16, borderRadius: 10, border: "1px solid var(--theme-elevation-150)", marginBottom: 20 }, children: [
142
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 13, fontWeight: 700, marginBottom: 8 }, children: t("timeDashboard.chart.title") }),
143
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "flex-end", gap: 3, height: 80 }, children: dailyChart.map((d) => /* @__PURE__ */ jsx("div", { style: { flex: 1, background: "#3b82f6", borderRadius: "3px 3px 0 0", height: `${Math.max(d.minutes / maxDailyMinutes * 100, 4)}%` }, title: `${d.day}: ${formatDuration(d.minutes)}` }, d.day)) })
144
+ ] }),
145
+ grouped.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: 40, textAlign: "center", color: "#94a3b8" }, children: t("timeDashboard.empty") }) : grouped.map((group) => /* @__PURE__ */ jsxs("div", { style: S.groupCard, children: [
146
+ /* @__PURE__ */ jsxs("div", { style: S.groupHeader, children: [
147
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 700 }, children: group.label }),
148
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 700, color: "#2563eb" }, children: formatDuration(group.totalMinutes) })
149
+ ] }),
150
+ /* @__PURE__ */ jsx("table", { style: S.table, children: /* @__PURE__ */ jsx("tbody", { children: group.entries.map((entry) => {
151
+ const ticket = typeof entry.ticket === "object" ? entry.ticket : null;
152
+ return /* @__PURE__ */ jsxs("tr", { children: [
153
+ /* @__PURE__ */ jsx("td", { style: S.td, children: ticket ? /* @__PURE__ */ jsx("a", { href: `/admin/collections/tickets/${ticket.id}`, style: { color: "#2563eb", textDecoration: "none", fontWeight: 600 }, children: ticket.ticketNumber || `#${ticket.id}` }) : "-" }),
154
+ /* @__PURE__ */ jsx("td", { style: S.td, children: ticket?.subject || "" }),
155
+ /* @__PURE__ */ jsx("td", { style: { ...S.td, color: "var(--theme-elevation-500)" }, children: entry.description || "-" }),
156
+ /* @__PURE__ */ jsx("td", { style: { ...S.td, textAlign: "right", fontWeight: 600 }, children: formatDuration(entry.duration) })
157
+ ] }, entry.id);
158
+ }) }) })
159
+ ] }, group.label))
160
+ ] })
161
+ ] });
162
+ };
163
+
164
+ export { TimeDashboardClient };
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var templates = require('@payloadcms/next/templates');
7
+ var navigation = require('next/navigation');
8
+ var ErrorBoundary = require('../shared/ErrorBoundary');
9
+ var client = require('./client');
10
+
11
+ const TimeDashboardView = ({ initPageResult }) => {
12
+ const { req, visibleEntities } = initPageResult;
13
+ if (!req.user) {
14
+ navigation.redirect("/admin/login");
15
+ }
16
+ return /* @__PURE__ */ jsxRuntime.jsx(
17
+ templates.DefaultTemplate,
18
+ {
19
+ i18n: req.i18n,
20
+ locale: initPageResult.locale,
21
+ params: {},
22
+ payload: req.payload,
23
+ permissions: initPageResult.permissions,
24
+ searchParams: {},
25
+ user: req.user,
26
+ visibleEntities,
27
+ children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary.AdminErrorBoundary, { viewName: "TimeDashboardView", children: /* @__PURE__ */ jsxRuntime.jsx(client.TimeDashboardClient, {}) })
28
+ }
29
+ );
30
+ };
31
+ var TimeDashboardView_default = TimeDashboardView;
32
+
33
+ exports.TimeDashboardView = TimeDashboardView;
34
+ exports.default = TimeDashboardView_default;
@@ -0,0 +1,29 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { DefaultTemplate } from '@payloadcms/next/templates';
3
+ import { redirect } from 'next/navigation';
4
+ import { AdminErrorBoundary } from '../shared/ErrorBoundary';
5
+ import { TimeDashboardClient } from './client';
6
+
7
+ const TimeDashboardView = ({ initPageResult }) => {
8
+ const { req, visibleEntities } = initPageResult;
9
+ if (!req.user) {
10
+ redirect("/admin/login");
11
+ }
12
+ return /* @__PURE__ */ jsx(
13
+ DefaultTemplate,
14
+ {
15
+ i18n: req.i18n,
16
+ locale: initPageResult.locale,
17
+ params: {},
18
+ payload: req.payload,
19
+ permissions: initPageResult.permissions,
20
+ searchParams: {},
21
+ user: req.user,
22
+ visibleEntities,
23
+ children: /* @__PURE__ */ jsx(AdminErrorBoundary, { viewName: "TimeDashboardView", children: /* @__PURE__ */ jsx(TimeDashboardClient, {}) })
24
+ }
25
+ );
26
+ };
27
+ var TimeDashboardView_default = TimeDashboardView;
28
+
29
+ export { TimeDashboardView, TimeDashboardView_default as default };
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var adminTokens = require('./adminTokens');
5
+
6
+ const AdminViewHeader = ({
7
+ icon,
8
+ title,
9
+ subtitle,
10
+ breadcrumb,
11
+ actions
12
+ }) => {
13
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14
+ "div",
15
+ {
16
+ style: {
17
+ display: "flex",
18
+ justifyContent: "space-between",
19
+ alignItems: "flex-start",
20
+ marginBottom: 20
21
+ },
22
+ children: [
23
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
24
+ breadcrumb && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginBottom: 6 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
25
+ "a",
26
+ {
27
+ href: breadcrumb.href,
28
+ style: {
29
+ fontSize: 12,
30
+ fontWeight: 700,
31
+ color: adminTokens.V.cyan,
32
+ textDecoration: "none",
33
+ textTransform: "uppercase"
34
+ },
35
+ children: [
36
+ "\u2190 ",
37
+ breadcrumb.label
38
+ ]
39
+ }
40
+ ) }),
41
+ /* @__PURE__ */ jsxRuntime.jsxs(
42
+ "h1",
43
+ {
44
+ style: {
45
+ fontSize: 28,
46
+ fontWeight: 600,
47
+ letterSpacing: 0,
48
+ margin: 0,
49
+ color: adminTokens.V.text,
50
+ display: "flex",
51
+ alignItems: "center",
52
+ gap: 10
53
+ },
54
+ children: [
55
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: adminTokens.V.cyan, display: "flex", alignItems: "center" }, children: icon }),
56
+ title
57
+ ]
58
+ }
59
+ ),
60
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: adminTokens.V.textSecondary, margin: "4px 0 0", fontSize: 14 }, children: subtitle })
61
+ ] }),
62
+ actions && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 8 }, children: actions })
63
+ ]
64
+ }
65
+ );
66
+ };
67
+
68
+ exports.AdminViewHeader = AdminViewHeader;
@@ -0,0 +1,67 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { V } from './adminTokens';
4
+
5
+ const AdminViewHeader = ({
6
+ icon,
7
+ title,
8
+ subtitle,
9
+ breadcrumb,
10
+ actions
11
+ }) => {
12
+ return /* @__PURE__ */ jsxs(
13
+ "div",
14
+ {
15
+ style: {
16
+ display: "flex",
17
+ justifyContent: "space-between",
18
+ alignItems: "flex-start",
19
+ marginBottom: 20
20
+ },
21
+ children: [
22
+ /* @__PURE__ */ jsxs("div", { children: [
23
+ breadcrumb && /* @__PURE__ */ jsx("div", { style: { marginBottom: 6 }, children: /* @__PURE__ */ jsxs(
24
+ "a",
25
+ {
26
+ href: breadcrumb.href,
27
+ style: {
28
+ fontSize: 12,
29
+ fontWeight: 700,
30
+ color: V.cyan,
31
+ textDecoration: "none",
32
+ textTransform: "uppercase"
33
+ },
34
+ children: [
35
+ "\u2190 ",
36
+ breadcrumb.label
37
+ ]
38
+ }
39
+ ) }),
40
+ /* @__PURE__ */ jsxs(
41
+ "h1",
42
+ {
43
+ style: {
44
+ fontSize: 28,
45
+ fontWeight: 600,
46
+ letterSpacing: 0,
47
+ margin: 0,
48
+ color: V.text,
49
+ display: "flex",
50
+ alignItems: "center",
51
+ gap: 10
52
+ },
53
+ children: [
54
+ /* @__PURE__ */ jsx("span", { style: { color: V.cyan, display: "flex", alignItems: "center" }, children: icon }),
55
+ title
56
+ ]
57
+ }
58
+ ),
59
+ subtitle && /* @__PURE__ */ jsx("p", { style: { color: V.textSecondary, margin: "4px 0 0", fontSize: 14 }, children: subtitle })
60
+ ] }),
61
+ actions && /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children: actions })
62
+ ]
63
+ }
64
+ );
65
+ };
66
+
67
+ export { AdminViewHeader };