@burdenoff/microfe-timecampus 2026.626.2 → 2026.703.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.
- package/dist/generated/wspace-operations.js +996 -816
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js +28 -0
- package/dist/generated/wspace-types.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/timecampus/TimeCampusRoutes.js +16 -12
- package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
- package/dist/timecampus/components/Modal.js +65 -0
- package/dist/timecampus/components/Modal.js.map +1 -0
- package/dist/timecampus/components/StatusBadge.js +28 -0
- package/dist/timecampus/components/StatusBadge.js.map +1 -0
- package/dist/timecampus/components/Toast.js +87 -0
- package/dist/timecampus/components/Toast.js.map +1 -0
- package/dist/timecampus/index.js +1 -1
- package/dist/timecampus/pages/HomePage.js +143 -135
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/applets/AppletStorePage.js +14 -19
- package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js +49 -37
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +315 -87
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/channels/ChannelsPage.js +340 -46
- package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +374 -92
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js +70 -37
- package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +366 -60
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +461 -87
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/geofencing/GeofencingPage.js +314 -55
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +30 -27
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +269 -74
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js +127 -0
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -0
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js +62 -52
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +361 -74
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingFormModal.js +156 -0
- package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -0
- package/dist/timecampus/pages/meetings/MeetingsPage.js +175 -74
- package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js +105 -0
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -0
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js +307 -67
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectCreatePage.js +51 -48
- package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectDetailPage.js +248 -51
- package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectsPage.js +354 -87
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js +367 -65
- package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +674 -104
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js +46 -34
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js +93 -69
- package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamPage.js +4 -3
- package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
- package/dist/timecampus/pages/templates/TemplateDetailPage.js +72 -53
- package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
- package/dist/timecampus/pages/templates/TemplatesPage.js +40 -35
- package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeEntriesPage.js +484 -97
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +210 -99
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +229 -97
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +315 -110
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js +250 -0
- package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -0
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js +158 -76
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
- package/dist/timecampus/pages/widgets/WidgetsPage.js +181 -52
- package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
- package/dist/timecampus/pages/workflows/WorkflowsPage.js +212 -61
- package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
- package/dist/timecampus/store/timecampusStore.js +13 -1
- package/dist/timecampus/store/timecampusStore.js.map +1 -1
- package/dist/timecampus/utils/helpers.js +20 -10
- package/dist/timecampus/utils/helpers.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,98 +3,131 @@ import { LoadingSkeleton as t } from "../../components/LoadingSkeleton.js";
|
|
|
3
3
|
import { useGetFocusScoreSummaryQuery as n, useGetTimeEntryStatsQuery as r, useGetWorkPatternSummaryQuery as i } from "../../../generated/wspace-operations.js";
|
|
4
4
|
import { PageLayout as a } from "../../components/PageLayout.js";
|
|
5
5
|
import { EmptyState as o } from "../../components/EmptyState.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { useState as s } from "react";
|
|
7
|
+
import { Download as c, FileText as l, RefreshCw as u } from "lucide-react";
|
|
8
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
9
|
+
import { GlassCard as p } from "@burdenoff/fe-libs/ui";
|
|
9
10
|
//#region src/timecampus/pages/dashboard/ReportsPage.tsx
|
|
10
|
-
function
|
|
11
|
-
let
|
|
12
|
-
|
|
11
|
+
function m(e, t) {
|
|
12
|
+
let n = (e) => `"${String(e).replace(/"/g, "\"\"")}"`, r = ["Metric,Value", ...t.map(([e, t]) => `${n(e)},${n(t)}`)].join("\n"), i = new Blob([r], { type: "text/csv;charset=utf-8;" }), a = URL.createObjectURL(i), o = document.createElement("a");
|
|
13
|
+
o.href = a, o.download = e, document.body.appendChild(o), o.click(), document.body.removeChild(o), URL.revokeObjectURL(a);
|
|
14
|
+
}
|
|
15
|
+
function h() {
|
|
16
|
+
let { workspaceId: h = "" } = e(), [g, _] = s(!1), { data: v, loading: y, error: b, refetch: x } = r({ skip: !h }), { data: S, loading: C, refetch: w } = i({ skip: !h }), { data: T, loading: E, refetch: D } = n({ skip: !h }), O = v?.timeEntryStats, k = S?.workPatternSummary, A = T?.focusScoreSummary, j = async () => {
|
|
17
|
+
_(!0);
|
|
18
|
+
try {
|
|
19
|
+
await Promise.all([
|
|
20
|
+
x(),
|
|
21
|
+
w(),
|
|
22
|
+
D()
|
|
23
|
+
]);
|
|
24
|
+
} finally {
|
|
25
|
+
_(!1);
|
|
26
|
+
}
|
|
27
|
+
}, M = (e) => {
|
|
28
|
+
(e === "time-summary" || e === "billable-summary") && O ? m(`${e}.csv`, [
|
|
29
|
+
["Total hours", Math.round(O.totalHours)],
|
|
30
|
+
["Billable hours", Math.round(O.billableHours)],
|
|
31
|
+
["Total entries", O.totalEntries],
|
|
32
|
+
["Active timers", O.activeTimers]
|
|
33
|
+
]) : e === "work-pattern" && k ? m("work-pattern-analysis.csv", [["Average focus hours", k.averageFocusHours.toFixed(1)], ["Average productivity %", Math.round(k.averageProductivity)]]) : e === "focus-score" && A && m("focus-score-report.csv", [
|
|
34
|
+
["Average score", Math.round(A.averageScore)],
|
|
35
|
+
["Highest score", Math.round(A.highestScore)],
|
|
36
|
+
["Lowest score", Math.round(A.lowestScore)]
|
|
37
|
+
]);
|
|
38
|
+
};
|
|
39
|
+
if (y || C || E) return /* @__PURE__ */ d(a, {
|
|
13
40
|
title: "Reports",
|
|
14
41
|
description: "Generate and view time tracking reports",
|
|
15
|
-
actions: /* @__PURE__ */
|
|
16
|
-
|
|
17
|
-
|
|
42
|
+
actions: /* @__PURE__ */ f("button", {
|
|
43
|
+
disabled: !0,
|
|
44
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text opacity-70",
|
|
45
|
+
children: [/* @__PURE__ */ d(u, { className: "h-4 w-4" }), "Refresh"]
|
|
18
46
|
}),
|
|
19
|
-
children: /* @__PURE__ */
|
|
47
|
+
children: /* @__PURE__ */ d(t, {
|
|
20
48
|
variant: "list",
|
|
21
49
|
count: 4
|
|
22
50
|
})
|
|
23
51
|
});
|
|
24
|
-
if (
|
|
52
|
+
if (b) return /* @__PURE__ */ d(a, {
|
|
25
53
|
title: "Reports",
|
|
26
54
|
description: "Generate and view time tracking reports",
|
|
27
|
-
children: /* @__PURE__ */
|
|
55
|
+
children: /* @__PURE__ */ d("div", {
|
|
28
56
|
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
29
57
|
children: "Failed to load reports data. Please try again later."
|
|
30
58
|
})
|
|
31
59
|
});
|
|
32
|
-
let
|
|
33
|
-
...
|
|
60
|
+
let N = [
|
|
61
|
+
...O ? [{
|
|
34
62
|
id: "time-summary",
|
|
35
63
|
name: "Time Tracking Summary",
|
|
36
64
|
type: "time",
|
|
37
|
-
period: `${Math.round(
|
|
65
|
+
period: `${Math.round(O.totalHours)}h total · ${O.totalEntries} entries`,
|
|
38
66
|
status: "ready"
|
|
39
67
|
}, {
|
|
40
68
|
id: "billable-summary",
|
|
41
69
|
name: "Billable Hours Report",
|
|
42
70
|
type: "billing",
|
|
43
|
-
period: `${Math.round(
|
|
71
|
+
period: `${Math.round(O.billableHours)}h billable · ${O.activeTimers} active`,
|
|
44
72
|
status: "ready"
|
|
45
73
|
}] : [],
|
|
46
|
-
...
|
|
74
|
+
...k ? [{
|
|
47
75
|
id: "work-pattern",
|
|
48
76
|
name: "Work Pattern Analysis",
|
|
49
77
|
type: "team",
|
|
50
|
-
period: `${
|
|
78
|
+
period: `${k.averageFocusHours.toFixed(1)}h avg focus · ${Math.round(k.averageProductivity)}% productivity`,
|
|
51
79
|
status: "ready"
|
|
52
80
|
}] : [],
|
|
53
|
-
...
|
|
81
|
+
...A ? [{
|
|
54
82
|
id: "focus-score",
|
|
55
83
|
name: "Focus Score Report",
|
|
56
84
|
type: "productivity",
|
|
57
|
-
period: `Avg: ${Math.round(
|
|
85
|
+
period: `Avg: ${Math.round(A.averageScore)} · High: ${Math.round(A.highestScore)} · Low: ${Math.round(A.lowestScore)}`,
|
|
58
86
|
status: "ready"
|
|
59
87
|
}] : []
|
|
60
88
|
];
|
|
61
|
-
return /* @__PURE__ */
|
|
89
|
+
return /* @__PURE__ */ d(a, {
|
|
62
90
|
title: "Reports",
|
|
63
91
|
description: "Generate and view time tracking reports",
|
|
64
|
-
purpose: "Package your tracked time into shareable, exportable reports — the deliverables you hand to clients for billing or to leadership for capacity and utilisation decisions.",
|
|
65
|
-
actions: /* @__PURE__ */
|
|
66
|
-
|
|
67
|
-
|
|
92
|
+
purpose: "Package your tracked time into shareable, exportable reports — the deliverables you hand to clients for billing or to leadership for capacity and utilisation decisions. Reports build automatically from your data; refresh to pull the latest, and download any report as CSV.",
|
|
93
|
+
actions: /* @__PURE__ */ f("button", {
|
|
94
|
+
onClick: j,
|
|
95
|
+
disabled: g,
|
|
96
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-70",
|
|
97
|
+
children: [/* @__PURE__ */ d(u, { className: `h-4 w-4 ${g ? "motion-safe:animate-spin" : ""}` }), g ? "Refreshing..." : "Refresh"]
|
|
68
98
|
}),
|
|
69
|
-
children:
|
|
99
|
+
children: N.length === 0 ? /* @__PURE__ */ d(o, {
|
|
70
100
|
illustration: "empty-data",
|
|
71
101
|
title: "No report data available yet",
|
|
72
102
|
description: "Track some time first — once you have entries, focus scores or work patterns, ready-to-export reports appear here."
|
|
73
|
-
}) : /* @__PURE__ */
|
|
103
|
+
}) : /* @__PURE__ */ d(p, {
|
|
74
104
|
treatment: "glass",
|
|
75
105
|
glow: !0,
|
|
76
106
|
className: "overflow-hidden",
|
|
77
|
-
children: /* @__PURE__ */
|
|
107
|
+
children: /* @__PURE__ */ d("div", {
|
|
78
108
|
className: "divide-y divide-border",
|
|
79
|
-
children:
|
|
109
|
+
children: N.map((e) => /* @__PURE__ */ f("div", {
|
|
80
110
|
className: "flex items-center justify-between p-4 hover:bg-bg-sunken/50 transition-colors",
|
|
81
|
-
children: [/* @__PURE__ */
|
|
111
|
+
children: [/* @__PURE__ */ f("div", {
|
|
82
112
|
className: "flex items-center gap-3",
|
|
83
|
-
children: [/* @__PURE__ */
|
|
113
|
+
children: [/* @__PURE__ */ d(l, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
|
|
84
114
|
className: "text-sm font-medium text-text-primary",
|
|
85
115
|
children: e.name
|
|
86
|
-
}), /* @__PURE__ */
|
|
116
|
+
}), /* @__PURE__ */ d("p", {
|
|
87
117
|
className: "text-xs text-text-secondary",
|
|
88
118
|
children: e.period
|
|
89
119
|
})] })]
|
|
90
|
-
}), /* @__PURE__ */
|
|
120
|
+
}), /* @__PURE__ */ f("div", {
|
|
91
121
|
className: "flex items-center gap-3",
|
|
92
|
-
children: [/* @__PURE__ */
|
|
122
|
+
children: [/* @__PURE__ */ d("span", {
|
|
93
123
|
className: "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-status-success-bg text-status-success-text",
|
|
94
124
|
children: e.status
|
|
95
|
-
}), /* @__PURE__ */
|
|
125
|
+
}), /* @__PURE__ */ d("button", {
|
|
126
|
+
onClick: () => M(e.id),
|
|
127
|
+
title: "Download as CSV",
|
|
128
|
+
"aria-label": `Download ${e.name} as CSV`,
|
|
96
129
|
className: "rounded-md p-1.5 hover:bg-bg-sunken",
|
|
97
|
-
children: /* @__PURE__ */
|
|
130
|
+
children: /* @__PURE__ */ d(c, { className: "h-4 w-4 text-text-secondary" })
|
|
98
131
|
})]
|
|
99
132
|
})]
|
|
100
133
|
}, e.id))
|
|
@@ -103,6 +136,6 @@ function p() {
|
|
|
103
136
|
});
|
|
104
137
|
}
|
|
105
138
|
//#endregion
|
|
106
|
-
export {
|
|
139
|
+
export { h as ReportsPage };
|
|
107
140
|
|
|
108
141
|
//# sourceMappingURL=ReportsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReportsPage.js","names":[],"sources":["../../../../src/timecampus/pages/dashboard/ReportsPage.tsx"],"sourcesContent":["import { FileText, Plus, Download } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useGetTimeEntryStatsQuery,\n useGetWorkPatternSummaryQuery,\n useGetFocusScoreSummaryQuery,\n} from \"@/generated/wspace-operations\";\n\nexport function ReportsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n } = useGetTimeEntryStatsQuery({\n skip: !workspaceId,\n });\n\n const { data: patternData, loading: patternLoading } =\n useGetWorkPatternSummaryQuery({\n skip: !workspaceId,\n });\n\n const { data: focusData, loading: focusLoading } =\n useGetFocusScoreSummaryQuery({\n skip: !workspaceId,\n });\n\n const stats = statsData?.timeEntryStats;\n const pattern = patternData?.workPatternSummary;\n const focus = focusData?.focusScoreSummary;\n\n const isLoading = statsLoading || patternLoading || focusLoading;\n\n if (isLoading) {\n return (\n <PageLayout\n title=\"Reports\"\n description=\"Generate and view time tracking reports\"\n actions={\n <button className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\">\n <Plus className=\"h-4 w-4\" />\n Generate Report\n </button>\n }\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (statsError) {\n return (\n <PageLayout\n title=\"Reports\"\n description=\"Generate and view time tracking reports\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load reports data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n // Build dynamic reports from available analytics data\n const reports = [\n ...(stats\n ? [\n {\n id: \"time-summary\",\n name: \"Time Tracking Summary\",\n type: \"time\",\n period: `${Math.round(stats.totalHours)}h total · ${stats.totalEntries} entries`,\n status: \"ready\" as const,\n },\n {\n id: \"billable-summary\",\n name: \"Billable Hours Report\",\n type: \"billing\",\n period: `${Math.round(stats.billableHours)}h billable · ${stats.activeTimers} active`,\n status: \"ready\" as const,\n },\n ]\n : []),\n ...(pattern\n ? [\n {\n id: \"work-pattern\",\n name: \"Work Pattern Analysis\",\n type: \"team\",\n period: `${pattern.averageFocusHours.toFixed(1)}h avg focus · ${Math.round(pattern.averageProductivity)}% productivity`,\n status: \"ready\" as const,\n },\n ]\n : []),\n ...(focus\n ? [\n {\n id: \"focus-score\",\n name: \"Focus Score Report\",\n type: \"productivity\",\n period: `Avg: ${Math.round(focus.averageScore)} · High: ${Math.round(focus.highestScore)} · Low: ${Math.round(focus.lowestScore)}`,\n status: \"ready\" as const,\n },\n ]\n : []),\n ];\n\n return (\n <PageLayout\n title=\"Reports\"\n description=\"Generate and view time tracking reports\"\n purpose=\"Package your tracked time into shareable, exportable reports — the deliverables you hand to clients for billing or to leadership for capacity and utilisation decisions.\"\n actions={\n <button className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\">\n <Plus className=\"h-4 w-4\" />\n Generate Report\n </button>\n }\n >\n {reports.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No report data available yet\"\n description=\"Track some time first — once you have entries, focus scores or work patterns, ready-to-export reports appear here.\"\n />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"divide-y divide-border\">\n {reports.map((report) => (\n <div\n key={report.id}\n className=\"flex items-center justify-between p-4 hover:bg-bg-sunken/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <FileText className=\"h-5 w-5 text-text-secondary\" />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {report.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {report.period}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-status-success-bg text-status-success-text\">\n {report.status}\n </span>\n <button className=\"rounded-md p-1.5 hover:bg-bg-sunken\">\n <Download className=\"h-4 w-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n ))}\n </div>\n </GlassCard>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;AAYA,SAAgB,IAAc;CAC5B,IAAM,EAAE,iBAAc,OAAO,GAAe,EAEtC,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,EAA0B,EAC5B,MAAM,CAAC,GACR,CAAC,EAEI,EAAE,MAAM,GAAa,SAAS,MAClC,EAA8B,EAC5B,MAAM,CAAC,GACR,CAAC,EAEE,EAAE,MAAM,GAAW,SAAS,MAChC,EAA6B,EAC3B,MAAM,CAAC,GACR,CAAC,EAEE,IAAQ,GAAW,gBACnB,IAAU,GAAa,oBACvB,IAAQ,GAAW;AAIzB,KAFkB,KAAgB,KAAkB,EAGlD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,kBAErB;;YAGX,kBAAC,GAAD;GAAiB,SAAQ;GAAO,OAAO;GAAK,CAAA;EACjC,CAAA;AAIjB,KAAI,EACF,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,OAAD;GAAK,WAAU;aAAoG;GAE7G,CAAA;EACK,CAAA;CAKjB,IAAM,IAAU;EACd,GAAI,IACA,CACE;GACE,IAAI;GACJ,MAAM;GACN,MAAM;GACN,QAAQ,GAAG,KAAK,MAAM,EAAM,WAAW,CAAC,YAAY,EAAM,aAAa;GACvE,QAAQ;GACT,EACD;GACE,IAAI;GACJ,MAAM;GACN,MAAM;GACN,QAAQ,GAAG,KAAK,MAAM,EAAM,cAAc,CAAC,eAAe,EAAM,aAAa;GAC7E,QAAQ;GACT,CACF,GACD,EAAE;EACN,GAAI,IACA,CACE;GACE,IAAI;GACJ,MAAM;GACN,MAAM;GACN,QAAQ,GAAG,EAAQ,kBAAkB,QAAQ,EAAE,CAAC,gBAAgB,KAAK,MAAM,EAAQ,oBAAoB,CAAC;GACxG,QAAQ;GACT,CACF,GACD,EAAE;EACN,GAAI,IACA,CACE;GACE,IAAI;GACJ,MAAM;GACN,MAAM;GACN,QAAQ,QAAQ,KAAK,MAAM,EAAM,aAAa,CAAC,WAAW,KAAK,MAAM,EAAM,aAAa,CAAC,UAAU,KAAK,MAAM,EAAM,YAAY;GAChI,QAAQ;GACT,CACF,GACD,EAAE;EACP;AAED,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;EACR,SACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,kBAErB;;YAGV,EAAQ,WAAW,IAClB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,CAAA,GAEF,kBAAC,GAAD;GAAW,WAAU;GAAQ,MAAA;GAAK,WAAU;aAC1C,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAQ,KAAK,MACZ,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,+BAAgC,CAAA,EACpD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAO;OACN,CAAA,EACJ,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAO;OACN,CAAA,CACA,EAAA,CAAA,CACF;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAO;OACH,CAAA,EACP,kBAAC,UAAD;OAAQ,WAAU;iBAChB,kBAAC,GAAD,EAAU,WAAU,+BAAgC,CAAA;OAC7C,CAAA,CACL;QACF;OAtBC,EAAO,GAsBR,CACN;IACE,CAAA;GACI,CAAA;EAEH,CAAA"}
|
|
1
|
+
{"version":3,"file":"ReportsPage.js","names":[],"sources":["../../../../src/timecampus/pages/dashboard/ReportsPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { FileText, RefreshCw, Download } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\n\n/** Trigger a client-side CSV download from an array of [key, value] rows. */\nfunction downloadCsv(filename: string, rows: Array<[string, string | number]>) {\n const escape = (v: string | number) => `\"${String(v).replace(/\"/g, '\"\"')}\"`;\n const csv = [\"Metric,Value\", ...rows.map(([k, v]) => `${escape(k)},${escape(v)}`)].join(\n \"\\n\",\n );\n const blob = new Blob([csv], { type: \"text/csv;charset=utf-8;\" });\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n}\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useGetTimeEntryStatsQuery,\n useGetWorkPatternSummaryQuery,\n useGetFocusScoreSummaryQuery,\n} from \"@/generated/wspace-operations\";\n\nexport function ReportsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const [refreshing, setRefreshing] = useState(false);\n\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n refetch: refetchStats,\n } = useGetTimeEntryStatsQuery({\n skip: !workspaceId,\n });\n\n const {\n data: patternData,\n loading: patternLoading,\n refetch: refetchPattern,\n } = useGetWorkPatternSummaryQuery({\n skip: !workspaceId,\n });\n\n const { data: focusData, loading: focusLoading, refetch: refetchFocus } =\n useGetFocusScoreSummaryQuery({\n skip: !workspaceId,\n });\n\n const stats = statsData?.timeEntryStats;\n const pattern = patternData?.workPatternSummary;\n const focus = focusData?.focusScoreSummary;\n\n const handleRefresh = async () => {\n setRefreshing(true);\n try {\n await Promise.all([refetchStats(), refetchPattern(), refetchFocus()]);\n } finally {\n setRefreshing(false);\n }\n };\n\n const handleDownload = (reportId: string) => {\n if ((reportId === \"time-summary\" || reportId === \"billable-summary\") && stats) {\n downloadCsv(`${reportId}.csv`, [\n [\"Total hours\", Math.round(stats.totalHours)],\n [\"Billable hours\", Math.round(stats.billableHours)],\n [\"Total entries\", stats.totalEntries],\n [\"Active timers\", stats.activeTimers],\n ]);\n } else if (reportId === \"work-pattern\" && pattern) {\n downloadCsv(\"work-pattern-analysis.csv\", [\n [\"Average focus hours\", pattern.averageFocusHours.toFixed(1)],\n [\"Average productivity %\", Math.round(pattern.averageProductivity)],\n ]);\n } else if (reportId === \"focus-score\" && focus) {\n downloadCsv(\"focus-score-report.csv\", [\n [\"Average score\", Math.round(focus.averageScore)],\n [\"Highest score\", Math.round(focus.highestScore)],\n [\"Lowest score\", Math.round(focus.lowestScore)],\n ]);\n }\n };\n\n const isLoading = statsLoading || patternLoading || focusLoading;\n\n if (isLoading) {\n return (\n <PageLayout\n title=\"Reports\"\n description=\"Generate and view time tracking reports\"\n actions={\n <button\n disabled\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text opacity-70\"\n >\n <RefreshCw className=\"h-4 w-4\" />\n Refresh\n </button>\n }\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (statsError) {\n return (\n <PageLayout\n title=\"Reports\"\n description=\"Generate and view time tracking reports\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load reports data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n // Build dynamic reports from available analytics data\n const reports = [\n ...(stats\n ? [\n {\n id: \"time-summary\",\n name: \"Time Tracking Summary\",\n type: \"time\",\n period: `${Math.round(stats.totalHours)}h total · ${stats.totalEntries} entries`,\n status: \"ready\" as const,\n },\n {\n id: \"billable-summary\",\n name: \"Billable Hours Report\",\n type: \"billing\",\n period: `${Math.round(stats.billableHours)}h billable · ${stats.activeTimers} active`,\n status: \"ready\" as const,\n },\n ]\n : []),\n ...(pattern\n ? [\n {\n id: \"work-pattern\",\n name: \"Work Pattern Analysis\",\n type: \"team\",\n period: `${pattern.averageFocusHours.toFixed(1)}h avg focus · ${Math.round(pattern.averageProductivity)}% productivity`,\n status: \"ready\" as const,\n },\n ]\n : []),\n ...(focus\n ? [\n {\n id: \"focus-score\",\n name: \"Focus Score Report\",\n type: \"productivity\",\n period: `Avg: ${Math.round(focus.averageScore)} · High: ${Math.round(focus.highestScore)} · Low: ${Math.round(focus.lowestScore)}`,\n status: \"ready\" as const,\n },\n ]\n : []),\n ];\n\n return (\n <PageLayout\n title=\"Reports\"\n description=\"Generate and view time tracking reports\"\n purpose=\"Package your tracked time into shareable, exportable reports — the deliverables you hand to clients for billing or to leadership for capacity and utilisation decisions. Reports build automatically from your data; refresh to pull the latest, and download any report as CSV.\"\n actions={\n <button\n onClick={handleRefresh}\n disabled={refreshing}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-70\"\n >\n <RefreshCw\n className={`h-4 w-4 ${refreshing ? \"motion-safe:animate-spin\" : \"\"}`}\n />\n {refreshing ? \"Refreshing...\" : \"Refresh\"}\n </button>\n }\n >\n {reports.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No report data available yet\"\n description=\"Track some time first — once you have entries, focus scores or work patterns, ready-to-export reports appear here.\"\n />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"divide-y divide-border\">\n {reports.map((report) => (\n <div\n key={report.id}\n className=\"flex items-center justify-between p-4 hover:bg-bg-sunken/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <FileText className=\"h-5 w-5 text-text-secondary\" />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {report.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {report.period}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-status-success-bg text-status-success-text\">\n {report.status}\n </span>\n <button\n onClick={() => handleDownload(report.id)}\n title=\"Download as CSV\"\n aria-label={`Download ${report.name} as CSV`}\n className=\"rounded-md p-1.5 hover:bg-bg-sunken\"\n >\n <Download className=\"h-4 w-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n ))}\n </div>\n </GlassCard>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|