@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.
Files changed (93) hide show
  1. package/dist/generated/wspace-operations.js +996 -816
  2. package/dist/generated/wspace-operations.js.map +1 -1
  3. package/dist/generated/wspace-types.js +28 -0
  4. package/dist/generated/wspace-types.js.map +1 -0
  5. package/dist/index.js +4 -4
  6. package/dist/timecampus/TimeCampusRoutes.js +16 -12
  7. package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
  8. package/dist/timecampus/components/Modal.js +65 -0
  9. package/dist/timecampus/components/Modal.js.map +1 -0
  10. package/dist/timecampus/components/StatusBadge.js +28 -0
  11. package/dist/timecampus/components/StatusBadge.js.map +1 -0
  12. package/dist/timecampus/components/Toast.js +87 -0
  13. package/dist/timecampus/components/Toast.js.map +1 -0
  14. package/dist/timecampus/index.js +1 -1
  15. package/dist/timecampus/pages/HomePage.js +143 -135
  16. package/dist/timecampus/pages/HomePage.js.map +1 -1
  17. package/dist/timecampus/pages/applets/AppletStorePage.js +14 -19
  18. package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
  19. package/dist/timecampus/pages/applets/InstalledAppletsPage.js +49 -37
  20. package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
  21. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +315 -87
  22. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
  23. package/dist/timecampus/pages/channels/ChannelsPage.js +340 -46
  24. package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
  25. package/dist/timecampus/pages/client-portal/ClientPortalPage.js +374 -92
  26. package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
  27. package/dist/timecampus/pages/dashboard/ReportsPage.js +70 -37
  28. package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
  29. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +366 -60
  30. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
  31. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +461 -87
  32. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
  33. package/dist/timecampus/pages/geofencing/GeofencingPage.js +314 -55
  34. package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
  35. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +30 -27
  36. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
  37. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +269 -74
  38. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
  39. package/dist/timecampus/pages/meetings/ActionItemFormModal.js +127 -0
  40. package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -0
  41. package/dist/timecampus/pages/meetings/MeetingCreatePage.js +62 -52
  42. package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
  43. package/dist/timecampus/pages/meetings/MeetingDetailPage.js +361 -74
  44. package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
  45. package/dist/timecampus/pages/meetings/MeetingFormModal.js +156 -0
  46. package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -0
  47. package/dist/timecampus/pages/meetings/MeetingsPage.js +175 -74
  48. package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
  49. package/dist/timecampus/pages/meetings/ParticipantFormModal.js +105 -0
  50. package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -0
  51. package/dist/timecampus/pages/pomodoro/PomodoroPage.js +307 -67
  52. package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
  53. package/dist/timecampus/pages/projects/ProjectCreatePage.js +51 -48
  54. package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
  55. package/dist/timecampus/pages/projects/ProjectDetailPage.js +248 -51
  56. package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
  57. package/dist/timecampus/pages/projects/ProjectsPage.js +354 -87
  58. package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
  59. package/dist/timecampus/pages/projects/TasksPage.js +367 -65
  60. package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
  61. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +674 -104
  62. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
  63. package/dist/timecampus/pages/team/TeamAnalyticsPage.js +46 -34
  64. package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
  65. package/dist/timecampus/pages/team/TeamMembersPage.js +93 -69
  66. package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
  67. package/dist/timecampus/pages/team/TeamPage.js +4 -3
  68. package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
  69. package/dist/timecampus/pages/templates/TemplateDetailPage.js +72 -53
  70. package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
  71. package/dist/timecampus/pages/templates/TemplatesPage.js +40 -35
  72. package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
  73. package/dist/timecampus/pages/time/TimeEntriesPage.js +484 -97
  74. package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
  75. package/dist/timecampus/pages/time/TimeTrackingPage.js +210 -99
  76. package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
  77. package/dist/timecampus/pages/time/TimerPage.js +229 -97
  78. package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
  79. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +315 -110
  80. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
  81. package/dist/timecampus/pages/timeline/TimelinePage.js +250 -0
  82. package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -0
  83. package/dist/timecampus/pages/widgets/WidgetDetailPage.js +158 -76
  84. package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
  85. package/dist/timecampus/pages/widgets/WidgetsPage.js +181 -52
  86. package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
  87. package/dist/timecampus/pages/workflows/WorkflowsPage.js +212 -61
  88. package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
  89. package/dist/timecampus/store/timecampusStore.js +13 -1
  90. package/dist/timecampus/store/timecampusStore.js.map +1 -1
  91. package/dist/timecampus/utils/helpers.js +20 -10
  92. package/dist/timecampus/utils/helpers.js.map +1 -1
  93. 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 { Download as s, FileText as c, Plus as l } from "lucide-react";
7
- import { jsx as u, jsxs as d } from "react/jsx-runtime";
8
- import { GlassCard as f } from "@burdenoff/fe-libs/ui";
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 p() {
11
- let { workspaceId: p = "" } = e(), { data: m, loading: h, error: g } = r({ skip: !p }), { data: _, loading: v } = i({ skip: !p }), { data: y, loading: b } = n({ skip: !p }), x = m?.timeEntryStats, S = _?.workPatternSummary, C = y?.focusScoreSummary;
12
- if (h || v || b) return /* @__PURE__ */ u(a, {
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__ */ d("button", {
16
- 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",
17
- children: [/* @__PURE__ */ u(l, { className: "h-4 w-4" }), "Generate Report"]
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__ */ u(t, {
47
+ children: /* @__PURE__ */ d(t, {
20
48
  variant: "list",
21
49
  count: 4
22
50
  })
23
51
  });
24
- if (g) return /* @__PURE__ */ u(a, {
52
+ if (b) return /* @__PURE__ */ d(a, {
25
53
  title: "Reports",
26
54
  description: "Generate and view time tracking reports",
27
- children: /* @__PURE__ */ u("div", {
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 w = [
33
- ...x ? [{
60
+ let N = [
61
+ ...O ? [{
34
62
  id: "time-summary",
35
63
  name: "Time Tracking Summary",
36
64
  type: "time",
37
- period: `${Math.round(x.totalHours)}h total · ${x.totalEntries} entries`,
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(x.billableHours)}h billable · ${x.activeTimers} active`,
71
+ period: `${Math.round(O.billableHours)}h billable · ${O.activeTimers} active`,
44
72
  status: "ready"
45
73
  }] : [],
46
- ...S ? [{
74
+ ...k ? [{
47
75
  id: "work-pattern",
48
76
  name: "Work Pattern Analysis",
49
77
  type: "team",
50
- period: `${S.averageFocusHours.toFixed(1)}h avg focus · ${Math.round(S.averageProductivity)}% productivity`,
78
+ period: `${k.averageFocusHours.toFixed(1)}h avg focus · ${Math.round(k.averageProductivity)}% productivity`,
51
79
  status: "ready"
52
80
  }] : [],
53
- ...C ? [{
81
+ ...A ? [{
54
82
  id: "focus-score",
55
83
  name: "Focus Score Report",
56
84
  type: "productivity",
57
- period: `Avg: ${Math.round(C.averageScore)} · High: ${Math.round(C.highestScore)} · Low: ${Math.round(C.lowestScore)}`,
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__ */ u(a, {
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__ */ d("button", {
66
- 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",
67
- children: [/* @__PURE__ */ u(l, { className: "h-4 w-4" }), "Generate Report"]
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: w.length === 0 ? /* @__PURE__ */ u(o, {
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__ */ u(f, {
103
+ }) : /* @__PURE__ */ d(p, {
74
104
  treatment: "glass",
75
105
  glow: !0,
76
106
  className: "overflow-hidden",
77
- children: /* @__PURE__ */ u("div", {
107
+ children: /* @__PURE__ */ d("div", {
78
108
  className: "divide-y divide-border",
79
- children: w.map((e) => /* @__PURE__ */ d("div", {
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__ */ d("div", {
111
+ children: [/* @__PURE__ */ f("div", {
82
112
  className: "flex items-center gap-3",
83
- children: [/* @__PURE__ */ u(c, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("p", {
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__ */ u("p", {
116
+ }), /* @__PURE__ */ d("p", {
87
117
  className: "text-xs text-text-secondary",
88
118
  children: e.period
89
119
  })] })]
90
- }), /* @__PURE__ */ d("div", {
120
+ }), /* @__PURE__ */ f("div", {
91
121
  className: "flex items-center gap-3",
92
- children: [/* @__PURE__ */ u("span", {
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__ */ u("button", {
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__ */ u(s, { className: "h-4 w-4 text-text-secondary" })
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 { p as ReportsPage };
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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"}