@consilioweb/payload-support 0.8.2 → 0.9.5
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.
- package/dist/components/RichTextEditor/index.cjs +233 -0
- package/dist/components/RichTextEditor/index.js +232 -0
- package/dist/components/TicketConversation/components/CodeBlock.cjs +24 -7
- package/dist/components/TicketConversation/components/CodeBlock.js +23 -9
- package/dist/index.cjs +19 -1
- package/dist/index.js +19 -1
- package/dist/views/BillingView/client.cjs +260 -103
- package/dist/views/BillingView/client.js +259 -103
- package/dist/views/ChatView/client.cjs +184 -137
- package/dist/views/ChatView/client.js +180 -137
- package/dist/views/CrmView/client.cjs +270 -122
- package/dist/views/CrmView/client.js +266 -122
- package/dist/views/EmailTrackingView/client.cjs +80 -69
- package/dist/views/EmailTrackingView/client.js +80 -70
- package/dist/views/ImportConversationView/client.cjs +127 -94
- package/dist/views/ImportConversationView/client.js +123 -94
- package/dist/views/LogsView/client.cjs +56 -58
- package/dist/views/LogsView/client.js +52 -58
- package/dist/views/NewTicketView/client.cjs +39 -55
- package/dist/views/NewTicketView/client.js +38 -55
- package/dist/views/PendingEmailsView/client.cjs +399 -102
- package/dist/views/PendingEmailsView/client.js +396 -103
- package/dist/views/SupportDashboardView/client.cjs +276 -137
- package/dist/views/SupportDashboardView/client.js +275 -137
- package/dist/views/TicketDetailView/client.cjs +487 -204
- package/dist/views/TicketDetailView/client.js +486 -204
- package/dist/views/TicketInboxView/client.cjs +62 -65
- package/dist/views/TicketInboxView/client.js +62 -66
- package/dist/views/TicketingSettingsView/client.cjs +10 -8
- package/dist/views/TicketingSettingsView/client.js +10 -8
- package/dist/views/TimeDashboardView/client.cjs +70 -59
- package/dist/views/TimeDashboardView/client.js +69 -59
- package/package.json +7 -3
- package/src/components/RichTextEditor/index.tsx +261 -0
- package/src/components/TicketConversation/components/CodeBlock.tsx +53 -14
- package/src/plugin.ts +2 -0
- package/src/utils/emailTemplate.ts +37 -0
- package/src/views/BillingView/client.tsx +362 -69
- package/src/views/ChatView/client.tsx +225 -140
- package/src/views/CrmView/client.tsx +447 -189
- package/src/views/EmailTrackingView/client.tsx +111 -71
- package/src/views/ImportConversationView/client.tsx +255 -70
- package/src/views/LogsView/client.tsx +85 -50
- package/src/views/NewTicketView/client.tsx +37 -53
- package/src/views/PendingEmailsView/client.tsx +512 -92
- package/src/views/SupportDashboardView/client.tsx +294 -134
- package/src/views/TicketDetailView/client.tsx +486 -213
- package/src/views/TicketInboxView/client.tsx +52 -61
- package/src/views/TicketingSettingsView/client.tsx +10 -9
- package/src/views/TimeDashboardView/client.tsx +184 -69
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var React = require('react');
|
|
5
|
+
var Skeleton = require('../shared/Skeleton');
|
|
5
6
|
var useTranslation = require('../../components/TicketConversation/hooks/useTranslation');
|
|
7
|
+
var styles = require('../../styles/TimeDashboard.module.scss');
|
|
6
8
|
|
|
7
9
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
10
|
|
|
9
11
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
12
|
+
var styles__default = /*#__PURE__*/_interopDefault(styles);
|
|
10
13
|
|
|
11
14
|
function formatDuration(minutes) {
|
|
12
15
|
const h = Math.floor(minutes / 60);
|
|
@@ -36,7 +39,11 @@ const TimeDashboardClient = () => {
|
|
|
36
39
|
const fetchEntries = React.useCallback(async () => {
|
|
37
40
|
setLoading(true);
|
|
38
41
|
try {
|
|
39
|
-
const params = new URLSearchParams({
|
|
42
|
+
const params = new URLSearchParams({
|
|
43
|
+
limit: "500",
|
|
44
|
+
depth: "2",
|
|
45
|
+
sort: "-date"
|
|
46
|
+
});
|
|
40
47
|
if (from) params.set("where[date][greater_than_equal]", from);
|
|
41
48
|
if (to) params.set("where[date][less_than_equal]", to);
|
|
42
49
|
const res = await fetch(`/api/time-entries?${params}`);
|
|
@@ -52,6 +59,7 @@ const TimeDashboardClient = () => {
|
|
|
52
59
|
fetchEntries();
|
|
53
60
|
}, [fetchEntries]);
|
|
54
61
|
const totalMinutes = entries.reduce((sum, e) => sum + (e.duration || 0), 0);
|
|
62
|
+
const totalEntries = entries.length;
|
|
55
63
|
const grouped = React__default.default.useMemo(() => {
|
|
56
64
|
const map = /* @__PURE__ */ new Map();
|
|
57
65
|
for (const entry of entries) {
|
|
@@ -69,13 +77,19 @@ const TimeDashboardClient = () => {
|
|
|
69
77
|
if (!map.has(key)) map.set(key, []);
|
|
70
78
|
map.get(key).push(entry);
|
|
71
79
|
}
|
|
72
|
-
return Array.from(map.entries()).map(([label, items]) => ({
|
|
80
|
+
return Array.from(map.entries()).map(([label, items]) => ({
|
|
81
|
+
label,
|
|
82
|
+
entries: items,
|
|
83
|
+
totalMinutes: items.reduce((sum, e) => sum + (e.duration || 0), 0)
|
|
84
|
+
}));
|
|
73
85
|
}, [entries, groupBy]);
|
|
74
86
|
const dailyChart = React__default.default.useMemo(() => {
|
|
75
87
|
const dayMap = /* @__PURE__ */ new Map();
|
|
76
88
|
for (const entry of entries) {
|
|
77
89
|
const day = entry.date ? entry.date.split("T")[0] : null;
|
|
78
|
-
if (day)
|
|
90
|
+
if (day) {
|
|
91
|
+
dayMap.set(day, (dayMap.get(day) || 0) + (entry.duration || 0));
|
|
92
|
+
}
|
|
79
93
|
}
|
|
80
94
|
return Array.from(dayMap.entries()).sort(([a], [b]) => a.localeCompare(b)).slice(-30).map(([day, mins]) => ({ day, minutes: mins }));
|
|
81
95
|
}, [entries]);
|
|
@@ -84,43 +98,32 @@ const TimeDashboardClient = () => {
|
|
|
84
98
|
setFrom(range.from);
|
|
85
99
|
setTo(range.to);
|
|
86
100
|
};
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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") })
|
|
101
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.page, children: [
|
|
102
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.header, children: [
|
|
103
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.headerLeft, children: [
|
|
104
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: styles__default.default.title, children: t("timeDashboard.title") }),
|
|
105
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: styles__default.default.subtitle, children: t("timeDashboard.subtitle") })
|
|
103
106
|
] }),
|
|
104
|
-
/* @__PURE__ */ jsxRuntime.jsx("a", { href: "/admin/collections/time-entries/create",
|
|
107
|
+
/* @__PURE__ */ jsxRuntime.jsx("a", { href: "/admin/collections/time-entries/create", className: styles__default.default.newEntryBtn, children: t("timeDashboard.newEntry") })
|
|
105
108
|
] }),
|
|
106
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
107
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
108
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", {
|
|
109
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", {
|
|
110
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", {
|
|
109
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.filters, children: [
|
|
110
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.quickPeriod, children: [
|
|
111
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { className: styles__default.default.btnPrimary, onClick: () => setPeriod(getMonthRange(0)), children: t("timeDashboard.filters.thisMonth") }),
|
|
112
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { className: styles__default.default.btnSecondary, onClick: () => setPeriod(getMonthRange(-1)), children: t("timeDashboard.filters.lastMonth") }),
|
|
113
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { className: styles__default.default.btnAmber, onClick: () => setPeriod(getMonthRange(-2)), children: t("timeDashboard.filters.twoMonthsAgo") })
|
|
111
114
|
] }),
|
|
112
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
113
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
114
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", {
|
|
115
|
-
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "date", value: from, onChange: (e) => setFrom(e.target.value),
|
|
115
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.filterRow, children: [
|
|
116
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.fieldGroup, children: [
|
|
117
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: styles__default.default.label, children: t("timeDashboard.filters.from") }),
|
|
118
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "date", value: from, onChange: (e) => setFrom(e.target.value), className: styles__default.default.input })
|
|
116
119
|
] }),
|
|
117
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
118
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", {
|
|
119
|
-
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "date", value: to, onChange: (e) => setTo(e.target.value),
|
|
120
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.fieldGroup, children: [
|
|
121
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: styles__default.default.label, children: t("timeDashboard.filters.to") }),
|
|
122
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "date", value: to, onChange: (e) => setTo(e.target.value), className: styles__default.default.input })
|
|
120
123
|
] }),
|
|
121
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
122
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", {
|
|
123
|
-
/* @__PURE__ */ jsxRuntime.jsxs("select", { value: groupBy, onChange: (e) => setGroupBy(e.target.value),
|
|
124
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.fieldGroup, children: [
|
|
125
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: styles__default.default.label, children: t("timeDashboard.filters.groupBy") }),
|
|
126
|
+
/* @__PURE__ */ jsxRuntime.jsxs("select", { value: groupBy, onChange: (e) => setGroupBy(e.target.value), className: styles__default.default.select, children: [
|
|
124
127
|
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "day", children: t("timeDashboard.filters.day") }),
|
|
125
128
|
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "week", children: t("timeDashboard.filters.week") }),
|
|
126
129
|
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "project", children: t("timeDashboard.filters.project") })
|
|
@@ -128,37 +131,45 @@ const TimeDashboardClient = () => {
|
|
|
128
131
|
] })
|
|
129
132
|
] })
|
|
130
133
|
] }),
|
|
131
|
-
loading ? /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
132
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
133
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
134
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
135
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
134
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.loading, children: /* @__PURE__ */ jsxRuntime.jsx(Skeleton.SkeletonDashboard, {}) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
135
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.kpis, children: [
|
|
136
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.kpiCardPrimary, children: [
|
|
137
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.kpiLabel, children: t("timeDashboard.kpis.totalTime") }),
|
|
138
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.kpiPrimary, children: formatDuration(totalMinutes) })
|
|
136
139
|
] }),
|
|
137
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
138
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
139
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
140
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.kpiCardAmber, children: [
|
|
141
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.kpiLabel, children: t("timeDashboard.kpis.entries") }),
|
|
142
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.kpiAmber, children: totalEntries })
|
|
140
143
|
] }),
|
|
141
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
142
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
143
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
144
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.kpiCardOrange, children: [
|
|
145
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.kpiLabel, children: t("timeDashboard.kpis.dailyAverage") }),
|
|
146
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.kpiOrange, children: dailyChart.length > 0 ? formatDuration(Math.round(totalMinutes / dailyChart.length)) : "-" })
|
|
144
147
|
] })
|
|
145
148
|
] }),
|
|
146
|
-
dailyChart.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
147
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
148
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
149
|
+
dailyChart.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.chartWrap, children: [
|
|
150
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.chartHeader, children: t("timeDashboard.chart.title") }),
|
|
151
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.chartBars, children: dailyChart.map((d) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
152
|
+
"div",
|
|
153
|
+
{
|
|
154
|
+
className: styles__default.default.chartBar,
|
|
155
|
+
style: { height: `${Math.max(d.minutes / maxDailyMinutes * 100, 4)}%` },
|
|
156
|
+
title: `${d.day}: ${formatDuration(d.minutes)}`
|
|
157
|
+
},
|
|
158
|
+
d.day
|
|
159
|
+
)) })
|
|
149
160
|
] }),
|
|
150
|
-
grouped.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
151
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
152
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", {
|
|
153
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", {
|
|
161
|
+
grouped.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.empty, children: t("timeDashboard.empty") }) : grouped.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.groupCard, children: [
|
|
162
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.groupHeader, children: [
|
|
163
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.groupLabel, children: group.label }),
|
|
164
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.groupTotal, children: formatDuration(group.totalMinutes) })
|
|
154
165
|
] }),
|
|
155
|
-
/* @__PURE__ */ jsxRuntime.jsx("table", {
|
|
166
|
+
/* @__PURE__ */ jsxRuntime.jsx("table", { className: styles__default.default.table, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: group.entries.map((entry) => {
|
|
156
167
|
const ticket = typeof entry.ticket === "object" ? entry.ticket : null;
|
|
157
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
158
|
-
/* @__PURE__ */ jsxRuntime.jsx("td", {
|
|
159
|
-
/* @__PURE__ */ jsxRuntime.jsx("td", {
|
|
160
|
-
/* @__PURE__ */ jsxRuntime.jsx("td", {
|
|
161
|
-
/* @__PURE__ */ jsxRuntime.jsx("td", {
|
|
168
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: styles__default.default.entryRow, children: [
|
|
169
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: styles__default.default.tdTicket, children: ticket ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: `/admin/collections/tickets/${ticket.id}`, className: styles__default.default.ticketLink, children: ticket.ticketNumber || `#${ticket.id}` }) : "-" }),
|
|
170
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: styles__default.default.tdSubject, children: ticket?.subject || "" }),
|
|
171
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: styles__default.default.tdDescription, children: entry.description || "-" }),
|
|
172
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: styles__default.default.tdDuration, children: formatDuration(entry.duration) })
|
|
162
173
|
] }, entry.id);
|
|
163
174
|
}) }) })
|
|
164
175
|
] }, group.label))
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import React, { useState, useCallback, useEffect } from 'react';
|
|
4
|
+
import { SkeletonDashboard } from '../shared/Skeleton.js';
|
|
4
5
|
import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
|
|
6
|
+
import styles from '../../styles/TimeDashboard.module.scss';
|
|
5
7
|
|
|
6
8
|
function formatDuration(minutes) {
|
|
7
9
|
const h = Math.floor(minutes / 60);
|
|
@@ -31,7 +33,11 @@ const TimeDashboardClient = () => {
|
|
|
31
33
|
const fetchEntries = useCallback(async () => {
|
|
32
34
|
setLoading(true);
|
|
33
35
|
try {
|
|
34
|
-
const params = new URLSearchParams({
|
|
36
|
+
const params = new URLSearchParams({
|
|
37
|
+
limit: "500",
|
|
38
|
+
depth: "2",
|
|
39
|
+
sort: "-date"
|
|
40
|
+
});
|
|
35
41
|
if (from) params.set("where[date][greater_than_equal]", from);
|
|
36
42
|
if (to) params.set("where[date][less_than_equal]", to);
|
|
37
43
|
const res = await fetch(`/api/time-entries?${params}`);
|
|
@@ -47,6 +53,7 @@ const TimeDashboardClient = () => {
|
|
|
47
53
|
fetchEntries();
|
|
48
54
|
}, [fetchEntries]);
|
|
49
55
|
const totalMinutes = entries.reduce((sum, e) => sum + (e.duration || 0), 0);
|
|
56
|
+
const totalEntries = entries.length;
|
|
50
57
|
const grouped = React.useMemo(() => {
|
|
51
58
|
const map = /* @__PURE__ */ new Map();
|
|
52
59
|
for (const entry of entries) {
|
|
@@ -64,13 +71,19 @@ const TimeDashboardClient = () => {
|
|
|
64
71
|
if (!map.has(key)) map.set(key, []);
|
|
65
72
|
map.get(key).push(entry);
|
|
66
73
|
}
|
|
67
|
-
return Array.from(map.entries()).map(([label, items]) => ({
|
|
74
|
+
return Array.from(map.entries()).map(([label, items]) => ({
|
|
75
|
+
label,
|
|
76
|
+
entries: items,
|
|
77
|
+
totalMinutes: items.reduce((sum, e) => sum + (e.duration || 0), 0)
|
|
78
|
+
}));
|
|
68
79
|
}, [entries, groupBy]);
|
|
69
80
|
const dailyChart = React.useMemo(() => {
|
|
70
81
|
const dayMap = /* @__PURE__ */ new Map();
|
|
71
82
|
for (const entry of entries) {
|
|
72
83
|
const day = entry.date ? entry.date.split("T")[0] : null;
|
|
73
|
-
if (day)
|
|
84
|
+
if (day) {
|
|
85
|
+
dayMap.set(day, (dayMap.get(day) || 0) + (entry.duration || 0));
|
|
86
|
+
}
|
|
74
87
|
}
|
|
75
88
|
return Array.from(dayMap.entries()).sort(([a], [b]) => a.localeCompare(b)).slice(-30).map(([day, mins]) => ({ day, minutes: mins }));
|
|
76
89
|
}, [entries]);
|
|
@@ -79,43 +92,32 @@ const TimeDashboardClient = () => {
|
|
|
79
92
|
setFrom(range.from);
|
|
80
93
|
setTo(range.to);
|
|
81
94
|
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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") })
|
|
95
|
+
return /* @__PURE__ */ jsxs("div", { className: styles.page, children: [
|
|
96
|
+
/* @__PURE__ */ jsxs("div", { className: styles.header, children: [
|
|
97
|
+
/* @__PURE__ */ jsxs("div", { className: styles.headerLeft, children: [
|
|
98
|
+
/* @__PURE__ */ jsx("h1", { className: styles.title, children: t("timeDashboard.title") }),
|
|
99
|
+
/* @__PURE__ */ jsx("p", { className: styles.subtitle, children: t("timeDashboard.subtitle") })
|
|
98
100
|
] }),
|
|
99
|
-
/* @__PURE__ */ jsx("a", { href: "/admin/collections/time-entries/create",
|
|
101
|
+
/* @__PURE__ */ jsx("a", { href: "/admin/collections/time-entries/create", className: styles.newEntryBtn, children: t("timeDashboard.newEntry") })
|
|
100
102
|
] }),
|
|
101
|
-
/* @__PURE__ */ jsxs("div", {
|
|
102
|
-
/* @__PURE__ */ jsxs("div", {
|
|
103
|
-
/* @__PURE__ */ jsx("button", {
|
|
104
|
-
/* @__PURE__ */ jsx("button", {
|
|
105
|
-
/* @__PURE__ */ jsx("button", {
|
|
103
|
+
/* @__PURE__ */ jsxs("div", { className: styles.filters, children: [
|
|
104
|
+
/* @__PURE__ */ jsxs("div", { className: styles.quickPeriod, children: [
|
|
105
|
+
/* @__PURE__ */ jsx("button", { className: styles.btnPrimary, onClick: () => setPeriod(getMonthRange(0)), children: t("timeDashboard.filters.thisMonth") }),
|
|
106
|
+
/* @__PURE__ */ jsx("button", { className: styles.btnSecondary, onClick: () => setPeriod(getMonthRange(-1)), children: t("timeDashboard.filters.lastMonth") }),
|
|
107
|
+
/* @__PURE__ */ jsx("button", { className: styles.btnAmber, onClick: () => setPeriod(getMonthRange(-2)), children: t("timeDashboard.filters.twoMonthsAgo") })
|
|
106
108
|
] }),
|
|
107
|
-
/* @__PURE__ */ jsxs("div", {
|
|
108
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
109
|
-
/* @__PURE__ */ jsx("label", {
|
|
110
|
-
/* @__PURE__ */ jsx("input", { type: "date", value: from, onChange: (e) => setFrom(e.target.value),
|
|
109
|
+
/* @__PURE__ */ jsxs("div", { className: styles.filterRow, children: [
|
|
110
|
+
/* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
|
|
111
|
+
/* @__PURE__ */ jsx("label", { className: styles.label, children: t("timeDashboard.filters.from") }),
|
|
112
|
+
/* @__PURE__ */ jsx("input", { type: "date", value: from, onChange: (e) => setFrom(e.target.value), className: styles.input })
|
|
111
113
|
] }),
|
|
112
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
113
|
-
/* @__PURE__ */ jsx("label", {
|
|
114
|
-
/* @__PURE__ */ jsx("input", { type: "date", value: to, onChange: (e) => setTo(e.target.value),
|
|
114
|
+
/* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
|
|
115
|
+
/* @__PURE__ */ jsx("label", { className: styles.label, children: t("timeDashboard.filters.to") }),
|
|
116
|
+
/* @__PURE__ */ jsx("input", { type: "date", value: to, onChange: (e) => setTo(e.target.value), className: styles.input })
|
|
115
117
|
] }),
|
|
116
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
117
|
-
/* @__PURE__ */ jsx("label", {
|
|
118
|
-
/* @__PURE__ */ jsxs("select", { value: groupBy, onChange: (e) => setGroupBy(e.target.value),
|
|
118
|
+
/* @__PURE__ */ jsxs("div", { className: styles.fieldGroup, children: [
|
|
119
|
+
/* @__PURE__ */ jsx("label", { className: styles.label, children: t("timeDashboard.filters.groupBy") }),
|
|
120
|
+
/* @__PURE__ */ jsxs("select", { value: groupBy, onChange: (e) => setGroupBy(e.target.value), className: styles.select, children: [
|
|
119
121
|
/* @__PURE__ */ jsx("option", { value: "day", children: t("timeDashboard.filters.day") }),
|
|
120
122
|
/* @__PURE__ */ jsx("option", { value: "week", children: t("timeDashboard.filters.week") }),
|
|
121
123
|
/* @__PURE__ */ jsx("option", { value: "project", children: t("timeDashboard.filters.project") })
|
|
@@ -123,37 +125,45 @@ const TimeDashboardClient = () => {
|
|
|
123
125
|
] })
|
|
124
126
|
] })
|
|
125
127
|
] }),
|
|
126
|
-
loading ? /* @__PURE__ */ jsx("div", {
|
|
127
|
-
/* @__PURE__ */ jsxs("div", {
|
|
128
|
-
/* @__PURE__ */ jsxs("div", {
|
|
129
|
-
/* @__PURE__ */ jsx("div", {
|
|
130
|
-
/* @__PURE__ */ jsx("div", {
|
|
128
|
+
loading ? /* @__PURE__ */ jsx("div", { className: styles.loading, children: /* @__PURE__ */ jsx(SkeletonDashboard, {}) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
129
|
+
/* @__PURE__ */ jsxs("div", { className: styles.kpis, children: [
|
|
130
|
+
/* @__PURE__ */ jsxs("div", { className: styles.kpiCardPrimary, children: [
|
|
131
|
+
/* @__PURE__ */ jsx("div", { className: styles.kpiLabel, children: t("timeDashboard.kpis.totalTime") }),
|
|
132
|
+
/* @__PURE__ */ jsx("div", { className: styles.kpiPrimary, children: formatDuration(totalMinutes) })
|
|
131
133
|
] }),
|
|
132
|
-
/* @__PURE__ */ jsxs("div", {
|
|
133
|
-
/* @__PURE__ */ jsx("div", {
|
|
134
|
-
/* @__PURE__ */ jsx("div", {
|
|
134
|
+
/* @__PURE__ */ jsxs("div", { className: styles.kpiCardAmber, children: [
|
|
135
|
+
/* @__PURE__ */ jsx("div", { className: styles.kpiLabel, children: t("timeDashboard.kpis.entries") }),
|
|
136
|
+
/* @__PURE__ */ jsx("div", { className: styles.kpiAmber, children: totalEntries })
|
|
135
137
|
] }),
|
|
136
|
-
/* @__PURE__ */ jsxs("div", {
|
|
137
|
-
/* @__PURE__ */ jsx("div", {
|
|
138
|
-
/* @__PURE__ */ jsx("div", {
|
|
138
|
+
/* @__PURE__ */ jsxs("div", { className: styles.kpiCardOrange, children: [
|
|
139
|
+
/* @__PURE__ */ jsx("div", { className: styles.kpiLabel, children: t("timeDashboard.kpis.dailyAverage") }),
|
|
140
|
+
/* @__PURE__ */ jsx("div", { className: styles.kpiOrange, children: dailyChart.length > 0 ? formatDuration(Math.round(totalMinutes / dailyChart.length)) : "-" })
|
|
139
141
|
] })
|
|
140
142
|
] }),
|
|
141
|
-
dailyChart.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
142
|
-
/* @__PURE__ */ jsx("div", {
|
|
143
|
-
/* @__PURE__ */ jsx("div", {
|
|
143
|
+
dailyChart.length > 0 && /* @__PURE__ */ jsxs("div", { className: styles.chartWrap, children: [
|
|
144
|
+
/* @__PURE__ */ jsx("div", { className: styles.chartHeader, children: t("timeDashboard.chart.title") }),
|
|
145
|
+
/* @__PURE__ */ jsx("div", { className: styles.chartBars, children: dailyChart.map((d) => /* @__PURE__ */ jsx(
|
|
146
|
+
"div",
|
|
147
|
+
{
|
|
148
|
+
className: styles.chartBar,
|
|
149
|
+
style: { height: `${Math.max(d.minutes / maxDailyMinutes * 100, 4)}%` },
|
|
150
|
+
title: `${d.day}: ${formatDuration(d.minutes)}`
|
|
151
|
+
},
|
|
152
|
+
d.day
|
|
153
|
+
)) })
|
|
144
154
|
] }),
|
|
145
|
-
grouped.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
146
|
-
/* @__PURE__ */ jsxs("div", {
|
|
147
|
-
/* @__PURE__ */ jsx("span", {
|
|
148
|
-
/* @__PURE__ */ jsx("span", {
|
|
155
|
+
grouped.length === 0 ? /* @__PURE__ */ jsx("div", { className: styles.empty, children: t("timeDashboard.empty") }) : grouped.map((group) => /* @__PURE__ */ jsxs("div", { className: styles.groupCard, children: [
|
|
156
|
+
/* @__PURE__ */ jsxs("div", { className: styles.groupHeader, children: [
|
|
157
|
+
/* @__PURE__ */ jsx("span", { className: styles.groupLabel, children: group.label }),
|
|
158
|
+
/* @__PURE__ */ jsx("span", { className: styles.groupTotal, children: formatDuration(group.totalMinutes) })
|
|
149
159
|
] }),
|
|
150
|
-
/* @__PURE__ */ jsx("table", {
|
|
160
|
+
/* @__PURE__ */ jsx("table", { className: styles.table, children: /* @__PURE__ */ jsx("tbody", { children: group.entries.map((entry) => {
|
|
151
161
|
const ticket = typeof entry.ticket === "object" ? entry.ticket : null;
|
|
152
|
-
return /* @__PURE__ */ jsxs("tr", { children: [
|
|
153
|
-
/* @__PURE__ */ jsx("td", {
|
|
154
|
-
/* @__PURE__ */ jsx("td", {
|
|
155
|
-
/* @__PURE__ */ jsx("td", {
|
|
156
|
-
/* @__PURE__ */ jsx("td", {
|
|
162
|
+
return /* @__PURE__ */ jsxs("tr", { className: styles.entryRow, children: [
|
|
163
|
+
/* @__PURE__ */ jsx("td", { className: styles.tdTicket, children: ticket ? /* @__PURE__ */ jsx("a", { href: `/admin/collections/tickets/${ticket.id}`, className: styles.ticketLink, children: ticket.ticketNumber || `#${ticket.id}` }) : "-" }),
|
|
164
|
+
/* @__PURE__ */ jsx("td", { className: styles.tdSubject, children: ticket?.subject || "" }),
|
|
165
|
+
/* @__PURE__ */ jsx("td", { className: styles.tdDescription, children: entry.description || "-" }),
|
|
166
|
+
/* @__PURE__ */ jsx("td", { className: styles.tdDuration, children: formatDuration(entry.duration) })
|
|
157
167
|
] }, entry.id);
|
|
158
168
|
}) }) })
|
|
159
169
|
] }, group.label))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@consilioweb/payload-support",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "Payload CMS plugin — professional support & ticketing system with AI, SLA, time tracking, live chat, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"scripts": {
|
|
53
53
|
"build": "tsup",
|
|
54
54
|
"typecheck": "tsc --noEmit",
|
|
55
|
-
"prepublishOnly": "
|
|
55
|
+
"prepublishOnly": "npm run build"
|
|
56
56
|
},
|
|
57
57
|
"keywords": [
|
|
58
58
|
"payload",
|
|
@@ -96,7 +96,8 @@
|
|
|
96
96
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
97
97
|
"payload": "^3.0.0",
|
|
98
98
|
"react": "^18.0.0 || ^19.0.0",
|
|
99
|
-
"react-dom": "^18.0.0 || ^19.0.0"
|
|
99
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
100
|
+
"lucide-react": ">=0.300.0"
|
|
100
101
|
},
|
|
101
102
|
"peerDependenciesMeta": {
|
|
102
103
|
"@payloadcms/ui": {
|
|
@@ -108,6 +109,9 @@
|
|
|
108
109
|
"@payloadcms/next": {
|
|
109
110
|
"optional": true
|
|
110
111
|
},
|
|
112
|
+
"lucide-react": {
|
|
113
|
+
"optional": true
|
|
114
|
+
},
|
|
111
115
|
"next": {
|
|
112
116
|
"optional": true
|
|
113
117
|
},
|