@burdenoff/microfe-timecampus 2026.801.1 → 2026.820.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 (155) hide show
  1. package/dist/generated/wspace-operations.d.ts +484 -861
  2. package/dist/generated/wspace-operations.d.ts.map +1 -1
  3. package/dist/generated/wspace-operations.js +1377 -1377
  4. package/dist/generated/wspace-operations.js.map +1 -1
  5. package/dist/generated/wspace-types.js.map +1 -1
  6. package/dist/index.js +5 -3
  7. package/dist/services/createSandboxAssistantTransport.js.map +1 -1
  8. package/dist/stories/EmptyState.stories.d.ts +0 -1
  9. package/dist/stories/LoadingSkeleton.stories.d.ts +0 -1
  10. package/dist/stories/StatCard.stories.d.ts +0 -1
  11. package/dist/stories/StatusBadge.stories.d.ts +0 -1
  12. package/dist/timecampus/AdminRoot.d.ts +8 -0
  13. package/dist/timecampus/AdminRoot.d.ts.map +1 -0
  14. package/dist/timecampus/AdminRoot.js +12 -0
  15. package/dist/timecampus/AdminRoot.js.map +1 -0
  16. package/dist/timecampus/AdminRoutes.d.ts +3 -0
  17. package/dist/timecampus/AdminRoutes.d.ts.map +1 -0
  18. package/dist/timecampus/AdminRoutes.js +8 -0
  19. package/dist/timecampus/AdminRoutes.js.map +1 -0
  20. package/dist/timecampus/TimeCampusRoot.d.ts +0 -1
  21. package/dist/timecampus/TimeCampusRoot.js +4 -2
  22. package/dist/timecampus/TimeCampusRoot.js.map +1 -1
  23. package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
  24. package/dist/timecampus/components/ChartCard.d.ts +0 -1
  25. package/dist/timecampus/components/ChartCard.js.map +1 -1
  26. package/dist/timecampus/components/DataTable.d.ts +0 -1
  27. package/dist/timecampus/components/EmptyState.d.ts +0 -1
  28. package/dist/timecampus/components/EmptyState.js.map +1 -1
  29. package/dist/timecampus/components/ErrorBoundary.d.ts +0 -1
  30. package/dist/timecampus/components/ErrorBoundary.js.map +1 -1
  31. package/dist/timecampus/components/FilterBar.d.ts +0 -1
  32. package/dist/timecampus/components/LoadingSkeleton.d.ts +0 -1
  33. package/dist/timecampus/components/LoadingSkeleton.js.map +1 -1
  34. package/dist/timecampus/components/Modal.d.ts +0 -1
  35. package/dist/timecampus/components/Modal.js +5 -3
  36. package/dist/timecampus/components/Modal.js.map +1 -1
  37. package/dist/timecampus/components/PageLayout.d.ts +0 -1
  38. package/dist/timecampus/components/PageLayout.js.map +1 -1
  39. package/dist/timecampus/components/StatCard.d.ts +0 -1
  40. package/dist/timecampus/components/StatCard.js.map +1 -1
  41. package/dist/timecampus/components/StatusBadge.d.ts +0 -1
  42. package/dist/timecampus/components/StatusBadge.js.map +1 -1
  43. package/dist/timecampus/components/Toast.d.ts +0 -1
  44. package/dist/timecampus/components/Toast.js.map +1 -1
  45. package/dist/timecampus/components/charts/BalanceWheel.js.map +1 -1
  46. package/dist/timecampus/components/charts/BarsChart.d.ts +0 -1
  47. package/dist/timecampus/components/charts/BarsChart.js.map +1 -1
  48. package/dist/timecampus/components/charts/ChartParts.d.ts +0 -1
  49. package/dist/timecampus/components/charts/ChartParts.js.map +1 -1
  50. package/dist/timecampus/components/charts/DonutChart.d.ts +0 -1
  51. package/dist/timecampus/components/charts/DonutChart.js.map +1 -1
  52. package/dist/timecampus/components/charts/Heatmap.d.ts +0 -1
  53. package/dist/timecampus/components/charts/Heatmap.js +29 -29
  54. package/dist/timecampus/components/charts/Heatmap.js.map +1 -1
  55. package/dist/timecampus/components/charts/ProgressMeter.d.ts +0 -1
  56. package/dist/timecampus/components/charts/ProgressMeter.js +2 -2
  57. package/dist/timecampus/components/charts/ProgressMeter.js.map +1 -1
  58. package/dist/timecampus/components/charts/RadialGauge.d.ts +0 -1
  59. package/dist/timecampus/components/charts/RadialGauge.js +2 -2
  60. package/dist/timecampus/components/charts/RadialGauge.js.map +1 -1
  61. package/dist/timecampus/components/charts/TrendChart.d.ts +0 -1
  62. package/dist/timecampus/components/charts/TrendChart.js.map +1 -1
  63. package/dist/timecampus/components/charts/WeeksGrid.js +2 -2
  64. package/dist/timecampus/components/charts/WeeksGrid.js.map +1 -1
  65. package/dist/timecampus/components/charts/chartTheme.js.map +1 -1
  66. package/dist/timecampus/components/charts/index.js +10 -9
  67. package/dist/timecampus/components/index.js +18 -17
  68. package/dist/timecampus/index.d.ts +3 -1
  69. package/dist/timecampus/index.d.ts.map +1 -1
  70. package/dist/timecampus/index.js +8 -5
  71. package/dist/timecampus/pages/HomePage.d.ts +0 -1
  72. package/dist/timecampus/pages/HomePage.js.map +1 -1
  73. package/dist/timecampus/pages/admin/AdminPage.d.ts +3 -0
  74. package/dist/timecampus/pages/admin/AdminPage.d.ts.map +1 -0
  75. package/dist/timecampus/pages/admin/AdminPage.js +235 -0
  76. package/dist/timecampus/pages/admin/AdminPage.js.map +1 -0
  77. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.d.ts.map +1 -1
  78. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
  79. package/dist/timecampus/pages/client-portal/ClientPortalPage.d.ts.map +1 -1
  80. package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
  81. package/dist/timecampus/pages/dashboard/AnalyticsPage.js.map +1 -1
  82. package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
  83. package/dist/timecampus/pages/email-tracking/EmailAnalyticsPage.js.map +1 -1
  84. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.d.ts.map +1 -1
  85. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
  86. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.d.ts.map +1 -1
  87. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
  88. package/dist/timecampus/pages/focus-score/FocusScorePage.js.map +1 -1
  89. package/dist/timecampus/pages/geofencing/GeofencingPage.d.ts.map +1 -1
  90. package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
  91. package/dist/timecampus/pages/invoicing/InvoicingPage.d.ts.map +1 -1
  92. package/dist/timecampus/pages/invoicing/InvoicingPage.js.map +1 -1
  93. package/dist/timecampus/pages/lifetime/DeadTimeRadarPage.js.map +1 -1
  94. package/dist/timecampus/pages/lifetime/FiniteLifePage.js.map +1 -1
  95. package/dist/timecampus/pages/lifetime/LifeBalancePage.js.map +1 -1
  96. package/dist/timecampus/pages/lifetime/LifeTimelinePage.js.map +1 -1
  97. package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js.map +1 -1
  98. package/dist/timecampus/pages/lifetime/RecommendationsPage.js +1 -1
  99. package/dist/timecampus/pages/lifetime/RecommendationsPage.js.map +1 -1
  100. package/dist/timecampus/pages/lifetime/TimeWellSpentPage.js.map +1 -1
  101. package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js +9 -6
  102. package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js.map +1 -1
  103. package/dist/timecampus/pages/lifetime/domains.d.ts +0 -1
  104. package/dist/timecampus/pages/lifetime/domains.js +1 -1
  105. package/dist/timecampus/pages/lifetime/domains.js.map +1 -1
  106. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.d.ts.map +1 -1
  107. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
  108. package/dist/timecampus/pages/meetings/ActionItemFormModal.d.ts +0 -1
  109. package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -1
  110. package/dist/timecampus/pages/meetings/MeetingAnalyticsPage.js +1 -1
  111. package/dist/timecampus/pages/meetings/MeetingAnalyticsPage.js.map +1 -1
  112. package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
  113. package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
  114. package/dist/timecampus/pages/meetings/MeetingFormModal.d.ts +0 -1
  115. package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -1
  116. package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
  117. package/dist/timecampus/pages/meetings/ParticipantFormModal.d.ts +0 -1
  118. package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -1
  119. package/dist/timecampus/pages/pomodoro/PomodoroAnalyticsPage.js.map +1 -1
  120. package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
  121. package/dist/timecampus/pages/pomodoro/PomodoroSettingsPage.js.map +1 -1
  122. package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
  123. package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
  124. package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
  125. package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
  126. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.d.ts.map +1 -1
  127. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
  128. package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
  129. package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
  130. package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
  131. package/dist/timecampus/pages/team-activity/TeamActivityPage.js.map +1 -1
  132. package/dist/timecampus/pages/time/TimeEntriesPage.d.ts.map +1 -1
  133. package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
  134. package/dist/timecampus/pages/time/TimeTrackingPage.js +1 -1
  135. package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
  136. package/dist/timecampus/pages/time/TimerPage.js +4 -4
  137. package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
  138. package/dist/timecampus/pages/time-approval/TimeApprovalPage.d.ts.map +1 -1
  139. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
  140. package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -1
  141. package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +1 -1
  142. package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
  143. package/dist/timecampus/providers/TimeCampusProvider.d.ts +0 -1
  144. package/dist/timecampus/providers/TimeCampusProvider.d.ts.map +1 -1
  145. package/dist/timecampus/providers/TimeCampusProvider.js +38 -35
  146. package/dist/timecampus/providers/TimeCampusProvider.js.map +1 -1
  147. package/dist/timecampus/store/timecampusStore.js.map +1 -1
  148. package/dist/timecampus/types/index.d.ts +4 -1
  149. package/dist/timecampus/types/index.d.ts.map +1 -1
  150. package/dist/timecampus/utils/helpers.d.ts +0 -1
  151. package/dist/timecampus/utils/helpers.js.map +1 -1
  152. package/dist/types/assistant.d.ts +0 -1
  153. package/dist/utils/pageContext.js +1 -1
  154. package/dist/utils/pageContext.js.map +1 -1
  155. package/package.json +60 -57
@@ -1 +1 @@
1
- {"version":3,"file":"TeamPage.js","names":[],"sources":["../../../../src/timecampus/pages/team/TeamPage.tsx"],"sourcesContent":["import { Users, Clock, TrendingUp, Activity } from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n ChartCard,\n LoadingSkeleton,\n DonutChart,\n ProgressMeter,\n} from \"../../components\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport { useGetTimeEntryStatsQuery } from \"@/generated/wspace-operations\";\n\nconst formatHrs = (v: number): string =>\n `${Number.isInteger(v) ? v : v.toFixed(1)}h`;\n\nexport function TeamPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n\n const { data, loading, error } = useGetTimeEntryStatsQuery({\n skip: !workspaceId,\n });\n\n const stats = data?.timeEntryStats;\n\n if (loading) {\n return (\n <PageLayout\n title=\"Team Overview\"\n description=\"Monitor team performance and activity\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Team Overview\"\n description=\"Monitor team performance and activity\"\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 team data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const totalHours = stats?.totalHours ?? 0;\n const billableHours = stats?.billableHours ?? 0;\n const nonBillableHours = Math.max(0, totalHours - billableHours);\n const totalH = Math.floor(totalHours);\n const totalM = Math.round((totalHours - totalH) * 60);\n const billablePercent =\n totalHours > 0 ? Math.round((billableHours / totalHours) * 100) : 0;\n\n const hoursBreakdown = [\n { name: \"Billable\", value: Math.round(billableHours * 10) / 10 },\n { name: \"Non-billable\", value: Math.round(nonBillableHours * 10) / 10 },\n ];\n\n return (\n <PageLayout\n title=\"Team Overview\"\n description=\"Monitor team performance and activity\"\n purpose=\"See how your whole team is spending its time at a glance — who's active, how many hours are billable, and where capacity is going — so you can balance workload and catch problems early.\"\n >\n <NextSteps\n storageKey=\"timecampus-team\"\n className=\"mb-6\"\n steps={[\n {\n id: \"members\",\n label: \"Manage team members\",\n description: \"Add people and check individual workloads.\",\n onClick: () => navigate(\"/team/members\"),\n },\n {\n id: \"analytics\",\n label: \"Open team analytics\",\n description: \"Compare utilisation and trends across the team.\",\n onClick: () => navigate(\"/team/analytics\"),\n },\n {\n id: \"activity\",\n label: \"Watch live team activity\",\n description: \"See what people are working on right now.\",\n onClick: () => navigate(\"/team-activity\"),\n },\n ]}\n />\n\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Total Entries\"\n value={String(stats?.totalEntries ?? 0)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Active Timers\"\n value={String(stats?.activeTimers ?? 0)}\n icon={<Activity className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n <StatCard\n title=\"Total Hours\"\n value={`${totalH}h ${totalM}m`}\n icon={<Clock className=\"h-5 w-5\" />}\n trend={{\n value: stats?.billableHours ?? 0,\n label: \"billable hours\",\n }}\n />\n <StatCard\n title=\"Billable Rate\"\n value={`${billablePercent}%`}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n trend={{ value: billablePercent, label: \"of total hours\" }}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Hours Breakdown\"\n description=\"How the team's tracked time splits by billability.\"\n >\n <DonutChart\n data={hoursBreakdown}\n centerValue={Math.round(totalHours * 10) / 10}\n centerLabel=\"Total tracked\"\n valueFormat={formatHrs}\n height={240}\n emptyLabel=\"No tracked hours yet\"\n />\n </ChartCard>\n\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <h3 className=\"mb-4 text-sm font-semibold text-text-primary\">\n Team Capacity & Activity\n </h3>\n <ProgressMeter\n label=\"Billable rate\"\n value={billableHours}\n max={totalHours > 0 ? totalHours : 1}\n higherIsWorse={false}\n thresholds={{ warning: 0.5, danger: 0.3 }}\n format={formatHrs}\n />\n <div className=\"mt-5 grid grid-cols-2 gap-3\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/40 p-3\">\n <p className=\"text-xs text-text-secondary\">Active timers</p>\n <p className=\"mt-1 text-lg font-semibold tabular-nums text-text-primary\">\n {stats?.activeTimers ?? 0}\n </p>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/40 p-3\">\n <p className=\"text-xs text-text-secondary\">Time entries</p>\n <p className=\"mt-1 text-lg font-semibold tabular-nums text-text-primary\">\n {stats?.totalEntries ?? 0}\n </p>\n </div>\n </div>\n <button\n onClick={() => navigate(\"/team-activity\")}\n className=\"mt-5 inline-flex w-full items-center justify-center gap-1.5 rounded-md bg-action-primary-bg px-4 py-2.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Activity className=\"h-4 w-4\" />\n Open live team activity\n </button>\n </div>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TeamPage.js","names":[],"sources":["../../../../src/timecampus/pages/team/TeamPage.tsx"],"sourcesContent":["import { Users, Clock, TrendingUp, Activity } from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n ChartCard,\n LoadingSkeleton,\n DonutChart,\n ProgressMeter,\n} from \"../../components\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport { useGetTimeEntryStatsQuery } from \"@/generated/wspace-operations\";\n\nconst formatHrs = (v: number): string =>\n `${Number.isInteger(v) ? v : v.toFixed(1)}h`;\n\nexport function TeamPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n\n const { data, loading, error } = useGetTimeEntryStatsQuery({\n skip: !workspaceId,\n });\n\n const stats = data?.timeEntryStats;\n\n if (loading) {\n return (\n <PageLayout\n title=\"Team Overview\"\n description=\"Monitor team performance and activity\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Team Overview\"\n description=\"Monitor team performance and activity\"\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 team data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const totalHours = stats?.totalHours ?? 0;\n const billableHours = stats?.billableHours ?? 0;\n const nonBillableHours = Math.max(0, totalHours - billableHours);\n const totalH = Math.floor(totalHours);\n const totalM = Math.round((totalHours - totalH) * 60);\n const billablePercent =\n totalHours > 0 ? Math.round((billableHours / totalHours) * 100) : 0;\n\n const hoursBreakdown = [\n { name: \"Billable\", value: Math.round(billableHours * 10) / 10 },\n { name: \"Non-billable\", value: Math.round(nonBillableHours * 10) / 10 },\n ];\n\n return (\n <PageLayout\n title=\"Team Overview\"\n description=\"Monitor team performance and activity\"\n purpose=\"See how your whole team is spending its time at a glance — who's active, how many hours are billable, and where capacity is going — so you can balance workload and catch problems early.\"\n >\n <NextSteps\n storageKey=\"timecampus-team\"\n className=\"mb-6\"\n steps={[\n {\n id: \"members\",\n label: \"Manage team members\",\n description: \"Add people and check individual workloads.\",\n onClick: () => navigate(\"/team/members\"),\n },\n {\n id: \"analytics\",\n label: \"Open team analytics\",\n description: \"Compare utilisation and trends across the team.\",\n onClick: () => navigate(\"/team/analytics\"),\n },\n {\n id: \"activity\",\n label: \"Watch live team activity\",\n description: \"See what people are working on right now.\",\n onClick: () => navigate(\"/team-activity\"),\n },\n ]}\n />\n\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Total Entries\"\n value={String(stats?.totalEntries ?? 0)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Active Timers\"\n value={String(stats?.activeTimers ?? 0)}\n icon={<Activity className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n <StatCard\n title=\"Total Hours\"\n value={`${totalH}h ${totalM}m`}\n icon={<Clock className=\"h-5 w-5\" />}\n trend={{\n value: stats?.billableHours ?? 0,\n label: \"billable hours\",\n }}\n />\n <StatCard\n title=\"Billable Rate\"\n value={`${billablePercent}%`}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n trend={{ value: billablePercent, label: \"of total hours\" }}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Hours Breakdown\"\n description=\"How the team's tracked time splits by billability.\"\n >\n <DonutChart\n data={hoursBreakdown}\n centerValue={Math.round(totalHours * 10) / 10}\n centerLabel=\"Total tracked\"\n valueFormat={formatHrs}\n height={240}\n emptyLabel=\"No tracked hours yet\"\n />\n </ChartCard>\n\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <h3 className=\"mb-4 text-sm font-semibold text-text-primary\">\n Team Capacity & Activity\n </h3>\n <ProgressMeter\n label=\"Billable rate\"\n value={billableHours}\n max={totalHours > 0 ? totalHours : 1}\n higherIsWorse={false}\n thresholds={{ warning: 0.5, danger: 0.3 }}\n format={formatHrs}\n />\n <div className=\"mt-5 grid grid-cols-2 gap-3\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/40 p-3\">\n <p className=\"text-xs text-text-secondary\">Active timers</p>\n <p className=\"mt-1 text-lg font-semibold tabular-nums text-text-primary\">\n {stats?.activeTimers ?? 0}\n </p>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/40 p-3\">\n <p className=\"text-xs text-text-secondary\">Time entries</p>\n <p className=\"mt-1 text-lg font-semibold tabular-nums text-text-primary\">\n {stats?.totalEntries ?? 0}\n </p>\n </div>\n </div>\n <button\n onClick={() => navigate(\"/team-activity\")}\n className=\"mt-5 inline-flex w-full items-center justify-center gap-1.5 rounded-md bg-action-primary-bg px-4 py-2.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Activity className=\"h-4 w-4\" />\n Open live team activity\n </button>\n </div>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TeamActivityPage.js","names":[],"sources":["../../../../src/timecampus/pages/team-activity/TeamActivityPage.tsx"],"sourcesContent":["import { Activity, Users, Clock, CheckCircle, Zap } from \"lucide-react\";\nimport {\n PageLayout,\n StatCard,\n ChartCard,\n StatusBadge,\n EmptyState,\n LoadingSkeleton,\n BarsChart,\n ProgressMeter,\n} from \"../../components\";\nimport { formatRelativeTime } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useGetTimeEntryStatsQuery,\n useListTeamWorkPatternsQuery,\n type TeamWorkPatternEntryFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst formatHrs = (v: number): string =>\n `${Number.isInteger(v) ? v : v.toFixed(1)}h`;\n\n// Fallback monogram/short-id for the rare case the identity service didn't\n// resolve a member (left the workspace, lookup failure, etc.) — the backend\n// (teamWorkPatterns) already substitutes a \"Member <shortId>\" display name in\n// that case, this just derives a matching two-letter monogram from it.\nconst monogram = (memberName: string): string =>\n memberName.slice(0, 2).toUpperCase();\n\nconst shortDate = (value: string): string =>\n new Date(value).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n });\n\nexport function TeamActivityPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n } = useGetTimeEntryStatsQuery({ skip: !workspaceId });\n // Team-wide (BOFF-4037): every active workspace member's recent activity,\n // each resolved to that member's real display name/avatar — never just\n // the calling actor's own patterns repeated under everyone else's name.\n const {\n data: patternsData,\n loading: patternsLoading,\n error: patternsError,\n } = useListTeamWorkPatternsQuery({\n variables: { pagination: { limit: 10 } },\n skip: !workspaceId,\n });\n\n const stats = statsData?.timeEntryStats;\n const entries = patternsData?.teamWorkPatterns ?? [];\n\n if (statsLoading && patternsLoading) {\n return (\n <PageLayout\n title=\"Team Activity\"\n description=\"Real-time team activity feed\"\n >\n <LoadingSkeleton variant=\"list\" count={5} />\n </PageLayout>\n );\n }\n\n if (statsError && patternsError) {\n return (\n <PageLayout\n title=\"Team Activity\"\n description=\"Real-time team activity feed\"\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 team activity data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n // Recent work patterns as a real stacked composition — sourced only from the\n // rows already fetched above (oldest → newest for a left-to-right timeline).\n const patternBars = [...entries].reverse().map(({ pattern }) => ({\n label: shortDate(pattern.date),\n focus: Math.round(pattern.focusHours * 10) / 10,\n meetings: Math.round(pattern.meetingHours * 10) / 10,\n breaks: Math.round(pattern.breakHours * 10) / 10,\n }));\n\n return (\n <PageLayout\n title=\"Team Activity\"\n description=\"Real-time team activity feed\"\n purpose=\"A live pulse of what your team is doing right now — who's tracking, recent deep-work sessions, and today's hours — so you stay connected to the team's progress without interrupting anyone.\"\n >\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Active Timers\"\n value={String(stats?.activeTimers ?? 0)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Total Entries\"\n value={String(stats?.totalEntries ?? 0)}\n icon={<CheckCircle className=\"h-5 w-5\" />}\n description=\"Today\"\n />\n <StatCard\n title=\"Team Hours\"\n value={formatHrs(Math.round(stats?.totalHours ?? 0))}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"Total tracked\"\n />\n <StatCard\n title=\"Billable Hours\"\n value={formatHrs(Math.round(stats?.billableHours ?? 0))}\n icon={<Activity className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n </div>\n\n {patternBars.length > 0 && (\n <ChartCard\n title=\"Recent Work Patterns\"\n description=\"Focus, meeting and break hours across the latest logged patterns.\"\n >\n <BarsChart\n data={patternBars}\n xKey=\"label\"\n stacked\n height={240}\n valueFormat={formatHrs}\n series={[\n { key: \"focus\", label: \"Focus\" },\n { key: \"meetings\", label: \"Meetings\" },\n { key: \"breaks\", label: \"Breaks\" },\n ]}\n emptyLabel=\"No work patterns yet\"\n />\n </ChartCard>\n )}\n\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-4 sm:p-6\">\n <h3 className=\"mb-4 text-sm font-semibold text-text-primary\">\n Activity Feed\n </h3>\n {entries.length === 0 && !patternsLoading ? (\n <EmptyState\n illustration=\"empty-notifications\"\n title=\"No recent activity yet\"\n description=\"As your team tracks time and completes focus sessions, their activity will stream in here.\"\n />\n ) : (\n <ul className=\"space-y-3\">\n {entries.map(\n (\n {\n memberName,\n memberAvatar,\n pattern,\n }: TeamWorkPatternEntryFieldsFragment,\n index: number,\n ) => {\n const isDeepWork = pattern.deepWorkSessions > 0;\n const hasVitals =\n Boolean(pattern.energyLevel) ||\n Boolean(pattern.mood) ||\n Boolean(pattern.stressLevel);\n return (\n <li\n key={`${pattern.id}-${index}`}\n className=\"rounded-lg border border-border-subtle bg-bg-surface/40 p-4\"\n >\n <div className=\"flex items-start gap-3\">\n {memberAvatar ? (\n <img\n src={memberAvatar}\n alt={memberName}\n className=\"mt-0.5 h-9 w-9 shrink-0 rounded-full border border-border-seam object-cover\"\n />\n ) : (\n <div className=\"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-xs font-semibold uppercase text-primary\">\n {monogram(memberName)}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n <span className=\"text-sm font-medium text-text-primary\">\n {memberName}\n </span>\n <span className=\"text-xs text-text-secondary\">\n {formatRelativeTime(pattern.createdAt)}\n </span>\n {isDeepWork && (\n <StatusBadge\n variant=\"success\"\n label=\"Deep work\"\n />\n )}\n </div>\n <p className=\"mt-1.5 flex items-center gap-1.5 text-sm font-medium text-text-primary\">\n {isDeepWork ? (\n <Zap className=\"h-3.5 w-3.5 text-primary\" />\n ) : (\n <Activity className=\"h-3.5 w-3.5 text-primary\" />\n )}\n {isDeepWork\n ? \"Completed deep work session\"\n : \"Logged work pattern\"}\n </p>\n <div className=\"mt-2 flex flex-wrap gap-x-4 gap-y-1 text-xs text-text-secondary\">\n <span>{pattern.focusHours.toFixed(1)}h focus</span>\n <span>\n {pattern.meetingHours.toFixed(1)}h meetings\n </span>\n {pattern.deepWorkSessions > 0 && (\n <span>\n {pattern.deepWorkSessions} deep sessions\n </span>\n )}\n {pattern.interruptions > 0 && (\n <span>{pattern.interruptions} interruptions</span>\n )}\n <span>{shortDate(pattern.date)}</span>\n </div>\n {pattern.productivityScore != null && (\n <div className=\"mt-3\">\n <div className=\"mb-1 flex items-center justify-between text-xs\">\n <span className=\"text-text-secondary\">\n Productivity\n </span>\n <span className=\"tabular-nums text-text-primary\">\n {Math.round(pattern.productivityScore)}%\n </span>\n </div>\n <ProgressMeter\n value={pattern.productivityScore}\n max={100}\n higherIsWorse={false}\n thresholds={{ warning: 0.6, danger: 0.4 }}\n showValue={false}\n size=\"sm\"\n />\n </div>\n )}\n {hasVitals && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {pattern.energyLevel && (\n <StatusBadge\n variant=\"info\"\n label={`Energy: ${pattern.energyLevel}`}\n />\n )}\n {pattern.mood && (\n <StatusBadge\n variant=\"muted\"\n label={`Mood: ${pattern.mood}`}\n />\n )}\n {pattern.stressLevel && (\n <StatusBadge\n variant=\"warning\"\n label={`Stress: ${pattern.stressLevel}`}\n />\n )}\n </div>\n )}\n </div>\n </div>\n </li>\n );\n },\n )}\n </ul>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TeamActivityPage.js","names":[],"sources":["../../../../src/timecampus/pages/team-activity/TeamActivityPage.tsx"],"sourcesContent":["import { Activity, Users, Clock, CheckCircle, Zap } from \"lucide-react\";\nimport {\n PageLayout,\n StatCard,\n ChartCard,\n StatusBadge,\n EmptyState,\n LoadingSkeleton,\n BarsChart,\n ProgressMeter,\n} from \"../../components\";\nimport { formatRelativeTime } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useGetTimeEntryStatsQuery,\n useListTeamWorkPatternsQuery,\n type TeamWorkPatternEntryFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst formatHrs = (v: number): string =>\n `${Number.isInteger(v) ? v : v.toFixed(1)}h`;\n\n// Fallback monogram/short-id for the rare case the identity service didn't\n// resolve a member (left the workspace, lookup failure, etc.) — the backend\n// (teamWorkPatterns) already substitutes a \"Member <shortId>\" display name in\n// that case, this just derives a matching two-letter monogram from it.\nconst monogram = (memberName: string): string =>\n memberName.slice(0, 2).toUpperCase();\n\nconst shortDate = (value: string): string =>\n new Date(value).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n });\n\nexport function TeamActivityPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n } = useGetTimeEntryStatsQuery({ skip: !workspaceId });\n // Team-wide (BOFF-4037): every active workspace member's recent activity,\n // each resolved to that member's real display name/avatar — never just\n // the calling actor's own patterns repeated under everyone else's name.\n const {\n data: patternsData,\n loading: patternsLoading,\n error: patternsError,\n } = useListTeamWorkPatternsQuery({\n variables: { pagination: { limit: 10 } },\n skip: !workspaceId,\n });\n\n const stats = statsData?.timeEntryStats;\n const entries = patternsData?.teamWorkPatterns ?? [];\n\n if (statsLoading && patternsLoading) {\n return (\n <PageLayout\n title=\"Team Activity\"\n description=\"Real-time team activity feed\"\n >\n <LoadingSkeleton variant=\"list\" count={5} />\n </PageLayout>\n );\n }\n\n if (statsError && patternsError) {\n return (\n <PageLayout\n title=\"Team Activity\"\n description=\"Real-time team activity feed\"\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 team activity data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n // Recent work patterns as a real stacked composition — sourced only from the\n // rows already fetched above (oldest → newest for a left-to-right timeline).\n const patternBars = [...entries].reverse().map(({ pattern }) => ({\n label: shortDate(pattern.date),\n focus: Math.round(pattern.focusHours * 10) / 10,\n meetings: Math.round(pattern.meetingHours * 10) / 10,\n breaks: Math.round(pattern.breakHours * 10) / 10,\n }));\n\n return (\n <PageLayout\n title=\"Team Activity\"\n description=\"Real-time team activity feed\"\n purpose=\"A live pulse of what your team is doing right now — who's tracking, recent deep-work sessions, and today's hours — so you stay connected to the team's progress without interrupting anyone.\"\n >\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Active Timers\"\n value={String(stats?.activeTimers ?? 0)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Total Entries\"\n value={String(stats?.totalEntries ?? 0)}\n icon={<CheckCircle className=\"h-5 w-5\" />}\n description=\"Today\"\n />\n <StatCard\n title=\"Team Hours\"\n value={formatHrs(Math.round(stats?.totalHours ?? 0))}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"Total tracked\"\n />\n <StatCard\n title=\"Billable Hours\"\n value={formatHrs(Math.round(stats?.billableHours ?? 0))}\n icon={<Activity className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n </div>\n\n {patternBars.length > 0 && (\n <ChartCard\n title=\"Recent Work Patterns\"\n description=\"Focus, meeting and break hours across the latest logged patterns.\"\n >\n <BarsChart\n data={patternBars}\n xKey=\"label\"\n stacked\n height={240}\n valueFormat={formatHrs}\n series={[\n { key: \"focus\", label: \"Focus\" },\n { key: \"meetings\", label: \"Meetings\" },\n { key: \"breaks\", label: \"Breaks\" },\n ]}\n emptyLabel=\"No work patterns yet\"\n />\n </ChartCard>\n )}\n\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-4 sm:p-6\">\n <h3 className=\"mb-4 text-sm font-semibold text-text-primary\">\n Activity Feed\n </h3>\n {entries.length === 0 && !patternsLoading ? (\n <EmptyState\n illustration=\"empty-notifications\"\n title=\"No recent activity yet\"\n description=\"As your team tracks time and completes focus sessions, their activity will stream in here.\"\n />\n ) : (\n <ul className=\"space-y-3\">\n {entries.map(\n (\n {\n memberName,\n memberAvatar,\n pattern,\n }: TeamWorkPatternEntryFieldsFragment,\n index: number,\n ) => {\n const isDeepWork = pattern.deepWorkSessions > 0;\n const hasVitals =\n Boolean(pattern.energyLevel) ||\n Boolean(pattern.mood) ||\n Boolean(pattern.stressLevel);\n return (\n <li\n key={`${pattern.id}-${index}`}\n className=\"rounded-lg border border-border-subtle bg-bg-surface/40 p-4\"\n >\n <div className=\"flex items-start gap-3\">\n {memberAvatar ? (\n <img\n src={memberAvatar}\n alt={memberName}\n className=\"mt-0.5 h-9 w-9 shrink-0 rounded-full border border-border-seam object-cover\"\n />\n ) : (\n <div className=\"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-xs font-semibold uppercase text-primary\">\n {monogram(memberName)}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n <span className=\"text-sm font-medium text-text-primary\">\n {memberName}\n </span>\n <span className=\"text-xs text-text-secondary\">\n {formatRelativeTime(pattern.createdAt)}\n </span>\n {isDeepWork && (\n <StatusBadge\n variant=\"success\"\n label=\"Deep work\"\n />\n )}\n </div>\n <p className=\"mt-1.5 flex items-center gap-1.5 text-sm font-medium text-text-primary\">\n {isDeepWork ? (\n <Zap className=\"h-3.5 w-3.5 text-primary\" />\n ) : (\n <Activity className=\"h-3.5 w-3.5 text-primary\" />\n )}\n {isDeepWork\n ? \"Completed deep work session\"\n : \"Logged work pattern\"}\n </p>\n <div className=\"mt-2 flex flex-wrap gap-x-4 gap-y-1 text-xs text-text-secondary\">\n <span>{pattern.focusHours.toFixed(1)}h focus</span>\n <span>\n {pattern.meetingHours.toFixed(1)}h meetings\n </span>\n {pattern.deepWorkSessions > 0 && (\n <span>\n {pattern.deepWorkSessions} deep sessions\n </span>\n )}\n {pattern.interruptions > 0 && (\n <span>{pattern.interruptions} interruptions</span>\n )}\n <span>{shortDate(pattern.date)}</span>\n </div>\n {pattern.productivityScore != null && (\n <div className=\"mt-3\">\n <div className=\"mb-1 flex items-center justify-between text-xs\">\n <span className=\"text-text-secondary\">\n Productivity\n </span>\n <span className=\"tabular-nums text-text-primary\">\n {Math.round(pattern.productivityScore)}%\n </span>\n </div>\n <ProgressMeter\n value={pattern.productivityScore}\n max={100}\n higherIsWorse={false}\n thresholds={{ warning: 0.6, danger: 0.4 }}\n showValue={false}\n size=\"sm\"\n />\n </div>\n )}\n {hasVitals && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {pattern.energyLevel && (\n <StatusBadge\n variant=\"info\"\n label={`Energy: ${pattern.energyLevel}`}\n />\n )}\n {pattern.mood && (\n <StatusBadge\n variant=\"muted\"\n label={`Mood: ${pattern.mood}`}\n />\n )}\n {pattern.stressLevel && (\n <StatusBadge\n variant=\"warning\"\n label={`Stress: ${pattern.stressLevel}`}\n />\n )}\n </div>\n )}\n </div>\n </div>\n </li>\n );\n },\n )}\n </ul>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAmBA,IAAM,KAAa,MACjB,GAAG,OAAO,UAAU,CAAC,IAAI,IAAI,EAAE,QAAQ,CAAC,EAAE,IAMtC,KAAY,MAChB,EAAW,MAAM,GAAG,CAAC,CAAC,CAAC,YAAY,GAE/B,KAAa,MACjB,IAAI,KAAK,CAAK,CAAC,CAAC,mBAAmB,KAAA,GAAW;CAC5C,OAAO;CACP,KAAK;AACP,CAAC;AAEH,SAAgB,IAAmB;CACjC,IAAM,EAAE,iBAAc,OAAO,EAAc,GAErC,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,EAA0B,EAAE,MAAM,CAAC,EAAY,CAAC,GAI9C,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,EAA6B;EAC/B,WAAW,EAAE,YAAY,EAAE,OAAO,GAAG,EAAE;EACvC,MAAM,CAAC;CACT,CAAC,GAEK,IAAQ,GAAW,gBACnB,IAAU,GAAc,oBAAoB,CAAC;CAEnD,IAAI,KAAgB,GAClB,OACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EAEZ,UAAA,kBAAC,GAAD;GAAiB,SAAQ;GAAO,OAAO;EAAI,CAAA;CACjC,CAAA;CAIhB,IAAI,KAAc,GAChB,OACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EAEZ,UAAA,kBAAC,OAAD;GAAK,WAAU;GAAoG,UAAA;EAE9G,CAAA;CACK,CAAA;CAMhB,IAAM,IAAc,CAAC,GAAG,CAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,kBAAe;EAC/D,OAAO,EAAU,EAAQ,IAAI;EAC7B,OAAO,KAAK,MAAM,EAAQ,aAAa,EAAE,IAAI;EAC7C,UAAU,KAAK,MAAM,EAAQ,eAAe,EAAE,IAAI;EAClD,QAAQ,KAAK,MAAM,EAAQ,aAAa,EAAE,IAAI;CAChD,EAAE;CAEF,OACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;EAER,UAAA,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA;MACE,kBAAC,GAAD;OACE,UAAA;OACA,OAAM;OACN,OAAO,OAAO,GAAO,gBAAgB,CAAC;OACtC,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;MACnC,CAAA;MACD,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,OAAO,GAAO,gBAAgB,CAAC;OACtC,MAAM,kBAAC,GAAD,EAAa,WAAU,UAAW,CAAA;OACxC,aAAY;MACb,CAAA;MACD,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,EAAU,KAAK,MAAM,GAAO,cAAc,CAAC,CAAC;OACnD,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;OAClC,aAAY;MACb,CAAA;MACD,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,EAAU,KAAK,MAAM,GAAO,iBAAiB,CAAC,CAAC;OACtD,MAAM,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;OACrC,aAAY;MACb,CAAA;KACE;;IAEJ,EAAY,SAAS,KACpB,kBAAC,GAAD;KACE,OAAM;KACN,aAAY;KAEZ,UAAA,kBAAC,GAAD;MACE,MAAM;MACN,MAAK;MACL,SAAA;MACA,QAAQ;MACR,aAAa;MACb,QAAQ;OACN;QAAE,KAAK;QAAS,OAAO;OAAQ;OAC/B;QAAE,KAAK;QAAY,OAAO;OAAW;OACrC;QAAE,KAAK;QAAU,OAAO;OAAS;MACnC;MACA,YAAW;KACZ,CAAA;IACQ,CAAA;IAGb,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;MAA+C,UAAA;KAEzD,CAAA,GACH,EAAQ,WAAW,KAAK,CAAC,IACxB,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;KACb,CAAA,IAED,kBAAC,MAAD;MAAI,WAAU;MACX,UAAA,EAAQ,KAEL,EACE,eACA,iBACA,cAEF,MACG;OACH,IAAM,IAAa,EAAQ,mBAAmB,GACxC,IACJ,EAAQ,EAAQ,eAChB,EAAQ,EAAQ,QAChB,EAAQ,EAAQ;OAClB,OACE,kBAAC,MAAD;QAEE,WAAU;QAEV,UAAA,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACG,IACC,kBAAC,OAAD;UACE,KAAK;UACL,KAAK;UACL,WAAU;SACX,CAAA,IAED,kBAAC,OAAD;UAAK,WAAU;UACZ,UAAA,EAAS,CAAU;SACjB,CAAA,GAEP,kBAAC,OAAD;UAAK,WAAU;UAAf,UAAA;WACE,kBAAC,OAAD;YAAK,WAAU;YAAf,UAAA;aACE,kBAAC,QAAD;cAAM,WAAU;cACb,UAAA;aACG,CAAA;aACN,kBAAC,QAAD;cAAM,WAAU;cACb,UAAA,EAAmB,EAAQ,SAAS;aACjC,CAAA;aACL,KACC,kBAAC,GAAD;cACE,SAAQ;cACR,OAAM;aACP,CAAA;YAEA;;WACL,kBAAC,KAAD;YAAG,WAAU;YAAb,UAAA,CAEI,EADD,IACE,IAEA,GAFD,EAAK,WAAU,2BAA4B,CAEK,GAEjD,IACG,gCACA,qBACH;;WACH,kBAAC,OAAD;YAAK,WAAU;YAAf,UAAA;aACE,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAQ,WAAW,QAAQ,CAAC,GAAE,SAAa,EAAA,CAAA;aAClD,kBAAC,QAAD,EAAA,UAAA,CACG,EAAQ,aAAa,QAAQ,CAAC,GAAE,YAC7B,EAAA,CAAA;aACL,EAAQ,mBAAmB,KAC1B,kBAAC,QAAD,EAAA,UAAA,CACG,EAAQ,kBAAiB,gBACtB,EAAA,CAAA;aAEP,EAAQ,gBAAgB,KACvB,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAQ,eAAc,gBAAoB,EAAA,CAAA;aAEnD,kBAAC,QAAD,EAAA,UAAO,EAAU,EAAQ,IAAI,EAAQ,CAAA;YAClC;;WACJ,EAAQ,qBAAqB,QAC5B,kBAAC,OAAD;YAAK,WAAU;YAAf,UAAA,CACE,kBAAC,OAAD;aAAK,WAAU;aAAf,UAAA,CACE,kBAAC,QAAD;cAAM,WAAU;cAAsB,UAAA;aAEhC,CAAA,GACN,kBAAC,QAAD;cAAM,WAAU;cAAhB,UAAA,CACG,KAAK,MAAM,EAAQ,iBAAiB,GAAE,GACnC;aACH,CAAA,CAAA;YACL,CAAA,GAAA,kBAAC,GAAD;aACE,OAAO,EAAQ;aACf,KAAK;aACL,eAAe;aACf,YAAY;cAAE,SAAS;cAAK,QAAQ;aAAI;aACxC,WAAW;aACX,MAAK;YACN,CAAA,CACE;;WAEN,KACC,kBAAC,OAAD;YAAK,WAAU;YAAf,UAAA;aACG,EAAQ,eACP,kBAAC,GAAD;cACE,SAAQ;cACR,OAAO,WAAW,EAAQ;aAC3B,CAAA;aAEF,EAAQ,QACP,kBAAC,GAAD;cACE,SAAQ;cACR,OAAO,SAAS,EAAQ;aACzB,CAAA;aAEF,EAAQ,eACP,kBAAC,GAAD;cACE,SAAQ;cACR,OAAO,WAAW,EAAQ;aAC3B,CAAA;YAEA;;UAEJ;SACF,CAAA,CAAA;;OACH,GAnGG,GAAG,EAAQ,GAAG,GAAG,GAmGpB;MAER,CACF;KACE,CAAA,CAEH;;GACF;;CACK,CAAA;AAEhB"}
@@ -1 +1 @@
1
- {"version":3,"file":"TimeEntriesPage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/time/TimeEntriesPage.tsx"],"names":[],"mappings":"AAyIA,wBAAgB,eAAe,gCA2jB9B"}
1
+ {"version":3,"file":"TimeEntriesPage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/time/TimeEntriesPage.tsx"],"names":[],"mappings":"AA0IA,wBAAgB,eAAe,gCA8jB9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"TimeEntriesPage.js","names":[],"sources":["../../../../src/timecampus/pages/time/TimeEntriesPage.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { Plus, Filter, Download, Pencil, Copy, Trash2 } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { Modal } from \"../../components/Modal\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { formatHours, formatDate, getInitials } from \"../../utils/helpers\";\nimport {\n useListTimeEntriesQuery,\n useCreateTimeEntryMutation,\n useUpdateTimeEntryMutation,\n useDeleteTimeEntryMutation,\n useDuplicateTimeEntryMutation,\n useListProjectsQuery,\n type TimeEntryFieldsFragment,\n type ListProjectsQuery,\n} from \"@/generated/wspace-operations\";\nimport type { TimeEntryFilterInput } from \"@/generated/wspace-types\";\n\ntype ProjectOption = ListProjectsQuery[\"projects\"][\"items\"][number];\n\nconst PAGE_SIZE = 50;\n\n/** Convert an ISO datetime to a value usable by <input type=\"datetime-local\">. */\nfunction toLocalInput(iso?: string | null): string {\n if (!iso) return \"\";\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 16);\n}\n\n/** Convert a datetime-local value to an ISO string, or undefined if empty. */\nfunction toISO(local: string): string | undefined {\n if (!local) return undefined;\n const d = new Date(local);\n if (Number.isNaN(d.getTime())) return undefined;\n return d.toISOString();\n}\n\ninterface EntryFormState {\n description: string;\n projectId: string;\n startTime: string;\n endTime: string;\n billable: boolean;\n}\n\nfunction emptyForm(): EntryFormState {\n const now = new Date();\n const local = new Date(now.getTime() - now.getTimezoneOffset() * 60000);\n return {\n description: \"\",\n projectId: \"\",\n startTime: local.toISOString().slice(0, 16),\n endTime: \"\",\n billable: false,\n };\n}\n\ninterface EntryFormFieldsProps {\n form: EntryFormState;\n onChange: (patch: Partial<EntryFormState>) => void;\n projects: ProjectOption[];\n}\n\nfunction EntryFormFields({ form, onChange, projects }: EntryFormFieldsProps) {\n return (\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Description\n </label>\n <input\n type=\"text\"\n placeholder=\"What did you work on?\"\n value={form.description}\n onChange={(e) => onChange({ description: e.target.value })}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Project\n </label>\n <select\n value={form.projectId}\n onChange={(e) => onChange({ projectId: e.target.value })}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"\">No Project</option>\n {projects.map((project) => (\n <option key={project.id} value={project.id}>\n {project.name}\n </option>\n ))}\n </select>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Start\n </label>\n <input\n type=\"datetime-local\"\n value={form.startTime}\n onChange={(e) => onChange({ startTime: e.target.value })}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n End\n </label>\n <input\n type=\"datetime-local\"\n value={form.endTime}\n onChange={(e) => onChange({ endTime: e.target.value })}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </div>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={form.billable}\n onChange={(e) => onChange({ billable: e.target.checked })}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n Billable\n </label>\n </div>\n );\n}\n\nexport function TimeEntriesPage() {\n // Filters\n const [filter, setFilter] = useState<TimeEntryFilterInput>({});\n const [filterOpen, setFilterOpen] = useState(false);\n const [draftBillable, setDraftBillable] = useState<\"all\" | \"yes\" | \"no\">(\n \"all\",\n );\n const [draftProjectId, setDraftProjectId] = useState(\"\");\n const [draftSearch, setDraftSearch] = useState(\"\");\n\n // Create / edit / delete modal state\n const [createOpen, setCreateOpen] = useState(false);\n const [createForm, setCreateForm] = useState<EntryFormState>(emptyForm);\n const [editEntry, setEditEntry] = useState<TimeEntryFieldsFragment | null>(\n null,\n );\n const [editForm, setEditForm] = useState<EntryFormState>(emptyForm);\n const [deleteEntry, setDeleteEntry] =\n useState<TimeEntryFieldsFragment | null>(null);\n\n const hasFilter = Object.keys(filter).length > 0;\n\n const { data, loading, error, fetchMore, refetch } = useListTimeEntriesQuery({\n variables: {\n pagination: { limit: PAGE_SIZE, offset: 0 },\n filter: hasFilter ? filter : undefined,\n },\n notifyOnNetworkStatusChange: true,\n });\n\n const { data: projectsData } = useListProjectsQuery({\n variables: { pagination: { limit: 100 } },\n });\n const projects: ProjectOption[] = projectsData?.projects?.items ?? [];\n\n const entries: TimeEntryFieldsFragment[] = useMemo(\n () => data?.timeEntries?.items ?? [],\n [data?.timeEntries?.items],\n );\n const hasMore = data?.timeEntries?.hasMore ?? false;\n const total = data?.timeEntries?.total ?? 0;\n\n const [createEntry, { loading: creating, error: createError }] =\n useCreateTimeEntryMutation();\n const [updateEntry, { loading: updating, error: updateError }] =\n useUpdateTimeEntryMutation();\n const [removeEntry, { loading: deleting }] = useDeleteTimeEntryMutation();\n const [duplicateEntry, { loading: duplicating }] =\n useDuplicateTimeEntryMutation();\n\n const [loadingMore, setLoadingMore] = useState(false);\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { limit: PAGE_SIZE, offset: entries.length } },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult) return prev;\n return {\n ...fetchMoreResult,\n timeEntries: {\n ...fetchMoreResult.timeEntries,\n items: [\n ...prev.timeEntries.items,\n ...fetchMoreResult.timeEntries.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const openCreate = () => {\n setCreateForm(emptyForm());\n setCreateOpen(true);\n };\n\n const handleCreate = async () => {\n const startTime = toISO(createForm.startTime);\n if (!startTime) return;\n await createEntry({\n variables: {\n input: {\n description: createForm.description.trim() || undefined,\n projectId: createForm.projectId || undefined,\n startTime,\n endTime: toISO(createForm.endTime),\n billable: createForm.billable,\n source: \"manual\",\n },\n },\n });\n setCreateOpen(false);\n await refetch();\n };\n\n const openEdit = (entry: TimeEntryFieldsFragment) => {\n setEditEntry(entry);\n setEditForm({\n description: entry.description ?? \"\",\n projectId: entry.projectId ?? \"\",\n startTime: toLocalInput(entry.startTime),\n endTime: toLocalInput(entry.endTime),\n billable: entry.billable,\n });\n };\n\n const handleUpdate = async () => {\n if (!editEntry) return;\n const startTime = toISO(editForm.startTime);\n if (!startTime) return;\n await updateEntry({\n variables: {\n id: editEntry.id,\n input: {\n description: editForm.description.trim() || undefined,\n projectId: editForm.projectId || undefined,\n startTime,\n endTime: toISO(editForm.endTime),\n billable: editForm.billable,\n },\n },\n });\n setEditEntry(null);\n await refetch();\n };\n\n const handleDuplicate = async (entry: TimeEntryFieldsFragment) => {\n await duplicateEntry({ variables: { id: entry.id } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!deleteEntry) return;\n await removeEntry({ variables: { id: deleteEntry.id } });\n setDeleteEntry(null);\n await refetch();\n };\n\n const applyFilter = () => {\n const next: TimeEntryFilterInput = {};\n if (draftBillable !== \"all\") next.billable = draftBillable === \"yes\";\n if (draftProjectId) next.projectId = draftProjectId;\n if (draftSearch.trim()) next.search = draftSearch.trim();\n setFilter(next);\n setFilterOpen(false);\n };\n\n const clearFilter = () => {\n setDraftBillable(\"all\");\n setDraftProjectId(\"\");\n setDraftSearch(\"\");\n setFilter({});\n setFilterOpen(false);\n };\n\n // Client-side CSV export of the currently loaded rows. There is no backend\n // export operation, so this exports what has been fetched into the table.\n const handleExport = () => {\n const header = [\n \"Date\",\n \"Project\",\n \"Task/Description\",\n \"Duration (hours)\",\n \"Billable\",\n \"Status\",\n ];\n const rows = entries.map((entry) => [\n formatDate(entry.startTime),\n entry.project?.name ?? \"No Project\",\n entry.task?.title ?? entry.description ?? \"\",\n ((entry.duration ?? 0) / 3600).toFixed(2),\n entry.billable ? \"Yes\" : \"No\",\n entry.isRunning ? \"running\" : \"completed\",\n ]);\n const escape = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n const csv = [header, ...rows]\n .map((row) => row.map((cell: string) => escape(String(cell))).join(\",\"))\n .join(\"\\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 = `time-entries-${new Date().toISOString().slice(0, 10)}.csv`;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n };\n\n if (error) {\n return (\n <PageLayout\n title=\"Time Entries\"\n description=\"View and manage all time entries\"\n >\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n Failed to load time entries. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Time Entries\"\n description=\"View and manage all time entries\"\n purpose=\"Every tracked block of work in one auditable list — filter, correct and confirm entries here before they roll into timesheets, approvals and client invoices.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => {\n setDraftBillable(\n filter.billable === undefined\n ? \"all\"\n : filter.billable\n ? \"yes\"\n : \"no\",\n );\n setDraftProjectId(filter.projectId ?? \"\");\n setDraftSearch(filter.search ?? \"\");\n setFilterOpen(true);\n }}\n className={`inline-flex items-center gap-1.5 rounded-md border px-3 py-2 text-sm font-medium hover:bg-bg-sunken/50 ${hasFilter ? \"border-action-primary-border text-primary\" : \"border-border-subtle text-text-primary\"}`}\n >\n <Filter className=\"h-4 w-4\" />\n Filter{hasFilter ? ` (${Object.keys(filter).length})` : \"\"}\n </button>\n <button\n onClick={handleExport}\n disabled={entries.length === 0}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n <Download className=\"h-4 w-4\" />\n Export\n </button>\n <button\n onClick={openCreate}\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\"\n >\n <Plus className=\"h-4 w-4\" />\n New Entry\n </button>\n </div>\n }\n >\n {loading && entries.length === 0 ? (\n <LoadingSkeleton variant=\"table\" count={5} />\n ) : entries.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No time entries found\"\n description=\"Track time with a timer or add a manual entry — your entries will appear here.\"\n action={\n <button\n onClick={openCreate}\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\"\n >\n <Plus className=\"h-4 w-4\" />\n New Entry\n </button>\n }\n />\n ) : (\n <>\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"border-b border-border-subtle\">\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary\">\n Entry\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary\">\n Project\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary\">\n Duration\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary\">\n Billable\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary\">\n Status\n </th>\n <th className=\"px-4 py-3 text-right text-xs font-medium text-text-secondary\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {entries.map((entry: TimeEntryFieldsFragment) => (\n <tr\n key={entry.id}\n className=\"hover:bg-bg-sunken/50 transition-colors\"\n >\n <td className=\"px-4 py-3\">\n <p className=\"text-sm font-medium text-text-primary\">\n {entry.task?.title ??\n entry.description ??\n \"No description\"}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {formatDate(entry.startTime)}\n </p>\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <span className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-action-primary-bg/10 text-xs font-semibold text-primary\">\n {getInitials(entry.project?.name ?? \"No Project\")}\n </span>\n <span className=\"text-sm font-medium text-text-primary\">\n {entry.project?.name ?? \"No Project\"}\n </span>\n </div>\n </td>\n <td className=\"px-4 py-3 text-sm font-mono text-text-primary\">\n {formatHours((entry.duration ?? 0) / 3600)}\n </td>\n <td className=\"px-4 py-3\">\n <StatusBadge\n label={entry.billable ? \"Billable\" : \"Non-billable\"}\n variant={entry.billable ? \"success\" : \"muted\"}\n />\n </td>\n <td className=\"px-4 py-3\">\n <StatusBadge\n status={entry.isRunning ? \"running\" : \"completed\"}\n dot={entry.isRunning}\n label={entry.isRunning ? \"running\" : \"completed\"}\n />\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center justify-end gap-1\">\n <button\n onClick={() => openEdit(entry)}\n title=\"Edit entry\"\n aria-label=\"Edit entry\"\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => handleDuplicate(entry)}\n disabled={duplicating}\n title=\"Duplicate entry\"\n aria-label=\"Duplicate entry\"\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-50\"\n >\n <Copy className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeleteEntry(entry)}\n title=\"Delete entry\"\n aria-label=\"Delete entry\"\n className=\"rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </GlassCard>\n\n <div className=\"mt-4 flex items-center justify-between\">\n <p className=\"text-xs text-text-secondary\">\n Showing {entries.length} of {total}\n </p>\n {hasMore && (\n <button\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n {loadingMore ? \"Loading...\" : \"Load more\"}\n </button>\n )}\n </div>\n </>\n )}\n\n {/* Create modal */}\n <Modal\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title=\"New Time Entry\"\n description=\"Manually log a block of work.\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => setCreateOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleCreate}\n disabled={creating || !createForm.startTime}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating ? \"Saving...\" : \"Create Entry\"}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to create entry. Please try again.\n </div>\n )}\n <EntryFormFields\n form={createForm}\n onChange={(patch) => setCreateForm((f) => ({ ...f, ...patch }))}\n projects={projects}\n />\n </Modal>\n\n {/* Edit modal */}\n <Modal\n open={Boolean(editEntry)}\n onClose={() => setEditEntry(null)}\n title=\"Edit Time Entry\"\n description=\"Correct the details of this logged block.\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => setEditEntry(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleUpdate}\n disabled={updating || !editForm.startTime}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {updating ? \"Saving...\" : \"Save Changes\"}\n </button>\n </>\n }\n >\n {updateError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to update entry. Please try again.\n </div>\n )}\n <EntryFormFields\n form={editForm}\n onChange={(patch) => setEditForm((f) => ({ ...f, ...patch }))}\n projects={projects}\n />\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={Boolean(deleteEntry)}\n onClose={() => setDeleteEntry(null)}\n title=\"Delete time entry?\"\n description=\"This permanently removes the entry. It cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setDeleteEntry(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-action-danger-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-danger-bg/90 disabled:opacity-50\"\n >\n {deleting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Delete the entry for{\" \"}\n <span className=\"font-medium text-text-primary\">\n {deleteEntry?.project?.name ??\n deleteEntry?.description ??\n \"this block of work\"}\n </span>\n ?\n </p>\n </Modal>\n\n {/* Filter modal */}\n <Modal\n open={filterOpen}\n onClose={() => setFilterOpen(false)}\n title=\"Filter entries\"\n description=\"Narrow the list to what you need.\"\n footer={\n <>\n <button\n onClick={clearFilter}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Clear\n </button>\n <button\n onClick={applyFilter}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n Apply\n </button>\n </>\n }\n >\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Search\n </label>\n <input\n type=\"text\"\n placeholder=\"Description contains...\"\n value={draftSearch}\n onChange={(e) => setDraftSearch(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Project\n </label>\n <select\n value={draftProjectId}\n onChange={(e) => setDraftProjectId(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"\">All Projects</option>\n {projects.map((project) => (\n <option key={project.id} value={project.id}>\n {project.name}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Billable\n </label>\n <select\n value={draftBillable}\n onChange={(e) =>\n setDraftBillable(e.target.value as \"all\" | \"yes\" | \"no\")\n }\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"all\">All</option>\n <option value=\"yes\">Billable only</option>\n <option value=\"no\">Non-billable only</option>\n </select>\n </div>\n </div>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TimeEntriesPage.js","names":[],"sources":["../../../../src/timecampus/pages/time/TimeEntriesPage.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { Plus, Filter, Download, Pencil, Copy, Trash2 } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { Modal } from \"../../components/Modal\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { formatHours, formatDate, getInitials } from \"../../utils/helpers\";\nimport {\n useListTimeEntriesQuery,\n useCreateTimeEntryMutation,\n useUpdateTimeEntryMutation,\n useDeleteTimeEntryMutation,\n useDuplicateTimeEntryMutation,\n useListProjectsQuery,\n type TimeEntryFieldsFragment,\n type ListProjectsQuery,\n type ListTimeEntriesQuery,\n} from \"@/generated/wspace-operations\";\nimport type { TimeEntryFilterInput } from \"@/generated/wspace-types\";\n\ntype ProjectOption = ListProjectsQuery[\"projects\"][\"items\"][number];\n\nconst PAGE_SIZE = 50;\n\n/** Convert an ISO datetime to a value usable by <input type=\"datetime-local\">. */\nfunction toLocalInput(iso?: string | null): string {\n if (!iso) return \"\";\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 16);\n}\n\n/** Convert a datetime-local value to an ISO string, or undefined if empty. */\nfunction toISO(local: string): string | undefined {\n if (!local) return undefined;\n const d = new Date(local);\n if (Number.isNaN(d.getTime())) return undefined;\n return d.toISOString();\n}\n\ninterface EntryFormState {\n description: string;\n projectId: string;\n startTime: string;\n endTime: string;\n billable: boolean;\n}\n\nfunction emptyForm(): EntryFormState {\n const now = new Date();\n const local = new Date(now.getTime() - now.getTimezoneOffset() * 60000);\n return {\n description: \"\",\n projectId: \"\",\n startTime: local.toISOString().slice(0, 16),\n endTime: \"\",\n billable: false,\n };\n}\n\ninterface EntryFormFieldsProps {\n form: EntryFormState;\n onChange: (patch: Partial<EntryFormState>) => void;\n projects: ProjectOption[];\n}\n\nfunction EntryFormFields({ form, onChange, projects }: EntryFormFieldsProps) {\n return (\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Description\n </label>\n <input\n type=\"text\"\n placeholder=\"What did you work on?\"\n value={form.description}\n onChange={(e) => onChange({ description: e.target.value })}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Project\n </label>\n <select\n value={form.projectId}\n onChange={(e) => onChange({ projectId: e.target.value })}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"\">No Project</option>\n {projects.map((project) => (\n <option key={project.id} value={project.id}>\n {project.name}\n </option>\n ))}\n </select>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Start\n </label>\n <input\n type=\"datetime-local\"\n value={form.startTime}\n onChange={(e) => onChange({ startTime: e.target.value })}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n End\n </label>\n <input\n type=\"datetime-local\"\n value={form.endTime}\n onChange={(e) => onChange({ endTime: e.target.value })}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </div>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={form.billable}\n onChange={(e) => onChange({ billable: e.target.checked })}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n Billable\n </label>\n </div>\n );\n}\n\nexport function TimeEntriesPage() {\n // Filters\n const [filter, setFilter] = useState<TimeEntryFilterInput>({});\n const [filterOpen, setFilterOpen] = useState(false);\n const [draftBillable, setDraftBillable] = useState<\"all\" | \"yes\" | \"no\">(\n \"all\",\n );\n const [draftProjectId, setDraftProjectId] = useState(\"\");\n const [draftSearch, setDraftSearch] = useState(\"\");\n\n // Create / edit / delete modal state\n const [createOpen, setCreateOpen] = useState(false);\n const [createForm, setCreateForm] = useState<EntryFormState>(emptyForm);\n const [editEntry, setEditEntry] = useState<TimeEntryFieldsFragment | null>(\n null,\n );\n const [editForm, setEditForm] = useState<EntryFormState>(emptyForm);\n const [deleteEntry, setDeleteEntry] =\n useState<TimeEntryFieldsFragment | null>(null);\n\n const hasFilter = Object.keys(filter).length > 0;\n\n const { data, loading, error, fetchMore, refetch } = useListTimeEntriesQuery({\n variables: {\n pagination: { limit: PAGE_SIZE, offset: 0 },\n filter: hasFilter ? filter : undefined,\n },\n notifyOnNetworkStatusChange: true,\n });\n\n const { data: projectsData } = useListProjectsQuery({\n variables: { pagination: { limit: 100 } },\n });\n const projects: ProjectOption[] = projectsData?.projects?.items ?? [];\n\n const entries: TimeEntryFieldsFragment[] = useMemo(\n () => data?.timeEntries?.items ?? [],\n [data?.timeEntries?.items],\n );\n const hasMore = data?.timeEntries?.hasMore ?? false;\n const total = data?.timeEntries?.total ?? 0;\n\n const [createEntry, { loading: creating, error: createError }] =\n useCreateTimeEntryMutation();\n const [updateEntry, { loading: updating, error: updateError }] =\n useUpdateTimeEntryMutation();\n const [removeEntry, { loading: deleting }] = useDeleteTimeEntryMutation();\n const [duplicateEntry, { loading: duplicating }] =\n useDuplicateTimeEntryMutation();\n\n const [loadingMore, setLoadingMore] = useState(false);\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { limit: PAGE_SIZE, offset: entries.length } },\n updateQuery: (\n prev: ListTimeEntriesQuery,\n { fetchMoreResult }: { fetchMoreResult?: ListTimeEntriesQuery },\n ) => {\n if (!fetchMoreResult) return prev;\n return {\n ...fetchMoreResult,\n timeEntries: {\n ...fetchMoreResult.timeEntries,\n items: [\n ...prev.timeEntries.items,\n ...fetchMoreResult.timeEntries.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const openCreate = () => {\n setCreateForm(emptyForm());\n setCreateOpen(true);\n };\n\n const handleCreate = async () => {\n const startTime = toISO(createForm.startTime);\n if (!startTime) return;\n await createEntry({\n variables: {\n input: {\n description: createForm.description.trim() || undefined,\n projectId: createForm.projectId || undefined,\n startTime,\n endTime: toISO(createForm.endTime),\n billable: createForm.billable,\n source: \"manual\",\n },\n },\n });\n setCreateOpen(false);\n await refetch();\n };\n\n const openEdit = (entry: TimeEntryFieldsFragment) => {\n setEditEntry(entry);\n setEditForm({\n description: entry.description ?? \"\",\n projectId: entry.projectId ?? \"\",\n startTime: toLocalInput(entry.startTime),\n endTime: toLocalInput(entry.endTime),\n billable: entry.billable,\n });\n };\n\n const handleUpdate = async () => {\n if (!editEntry) return;\n const startTime = toISO(editForm.startTime);\n if (!startTime) return;\n await updateEntry({\n variables: {\n id: editEntry.id,\n input: {\n description: editForm.description.trim() || undefined,\n projectId: editForm.projectId || undefined,\n startTime,\n endTime: toISO(editForm.endTime),\n billable: editForm.billable,\n },\n },\n });\n setEditEntry(null);\n await refetch();\n };\n\n const handleDuplicate = async (entry: TimeEntryFieldsFragment) => {\n await duplicateEntry({ variables: { id: entry.id } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!deleteEntry) return;\n await removeEntry({ variables: { id: deleteEntry.id } });\n setDeleteEntry(null);\n await refetch();\n };\n\n const applyFilter = () => {\n const next: TimeEntryFilterInput = {};\n if (draftBillable !== \"all\") next.billable = draftBillable === \"yes\";\n if (draftProjectId) next.projectId = draftProjectId;\n if (draftSearch.trim()) next.search = draftSearch.trim();\n setFilter(next);\n setFilterOpen(false);\n };\n\n const clearFilter = () => {\n setDraftBillable(\"all\");\n setDraftProjectId(\"\");\n setDraftSearch(\"\");\n setFilter({});\n setFilterOpen(false);\n };\n\n // Client-side CSV export of the currently loaded rows. There is no backend\n // export operation, so this exports what has been fetched into the table.\n const handleExport = () => {\n const header = [\n \"Date\",\n \"Project\",\n \"Task/Description\",\n \"Duration (hours)\",\n \"Billable\",\n \"Status\",\n ];\n const rows = entries.map((entry) => [\n formatDate(entry.startTime),\n entry.project?.name ?? \"No Project\",\n entry.task?.title ?? entry.description ?? \"\",\n ((entry.duration ?? 0) / 3600).toFixed(2),\n entry.billable ? \"Yes\" : \"No\",\n entry.isRunning ? \"running\" : \"completed\",\n ]);\n const escape = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n const csv = [header, ...rows]\n .map((row) => row.map((cell: string) => escape(String(cell))).join(\",\"))\n .join(\"\\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 = `time-entries-${new Date().toISOString().slice(0, 10)}.csv`;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n };\n\n if (error) {\n return (\n <PageLayout\n title=\"Time Entries\"\n description=\"View and manage all time entries\"\n >\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n Failed to load time entries. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Time Entries\"\n description=\"View and manage all time entries\"\n purpose=\"Every tracked block of work in one auditable list — filter, correct and confirm entries here before they roll into timesheets, approvals and client invoices.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => {\n setDraftBillable(\n filter.billable === undefined\n ? \"all\"\n : filter.billable\n ? \"yes\"\n : \"no\",\n );\n setDraftProjectId(filter.projectId ?? \"\");\n setDraftSearch(filter.search ?? \"\");\n setFilterOpen(true);\n }}\n className={`inline-flex items-center gap-1.5 rounded-md border px-3 py-2 text-sm font-medium hover:bg-bg-sunken/50 ${hasFilter ? \"border-action-primary-border text-primary\" : \"border-border-subtle text-text-primary\"}`}\n >\n <Filter className=\"h-4 w-4\" />\n Filter{hasFilter ? ` (${Object.keys(filter).length})` : \"\"}\n </button>\n <button\n onClick={handleExport}\n disabled={entries.length === 0}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n <Download className=\"h-4 w-4\" />\n Export\n </button>\n <button\n onClick={openCreate}\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\"\n >\n <Plus className=\"h-4 w-4\" />\n New Entry\n </button>\n </div>\n }\n >\n {loading && entries.length === 0 ? (\n <LoadingSkeleton variant=\"table\" count={5} />\n ) : entries.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No time entries found\"\n description=\"Track time with a timer or add a manual entry — your entries will appear here.\"\n action={\n <button\n onClick={openCreate}\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\"\n >\n <Plus className=\"h-4 w-4\" />\n New Entry\n </button>\n }\n />\n ) : (\n <>\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead>\n <tr className=\"border-b border-border-subtle\">\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary\">\n Entry\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary\">\n Project\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary\">\n Duration\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary\">\n Billable\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary\">\n Status\n </th>\n <th className=\"px-4 py-3 text-right text-xs font-medium text-text-secondary\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {entries.map((entry: TimeEntryFieldsFragment) => (\n <tr\n key={entry.id}\n className=\"hover:bg-bg-sunken/50 transition-colors\"\n >\n <td className=\"px-4 py-3\">\n <p className=\"text-sm font-medium text-text-primary\">\n {entry.task?.title ??\n entry.description ??\n \"No description\"}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {formatDate(entry.startTime)}\n </p>\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <span className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-action-primary-bg/10 text-xs font-semibold text-primary\">\n {getInitials(entry.project?.name ?? \"No Project\")}\n </span>\n <span className=\"text-sm font-medium text-text-primary\">\n {entry.project?.name ?? \"No Project\"}\n </span>\n </div>\n </td>\n <td className=\"px-4 py-3 text-sm font-mono text-text-primary\">\n {formatHours((entry.duration ?? 0) / 3600)}\n </td>\n <td className=\"px-4 py-3\">\n <StatusBadge\n label={entry.billable ? \"Billable\" : \"Non-billable\"}\n variant={entry.billable ? \"success\" : \"muted\"}\n />\n </td>\n <td className=\"px-4 py-3\">\n <StatusBadge\n status={entry.isRunning ? \"running\" : \"completed\"}\n dot={entry.isRunning}\n label={entry.isRunning ? \"running\" : \"completed\"}\n />\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center justify-end gap-1\">\n <button\n onClick={() => openEdit(entry)}\n title=\"Edit entry\"\n aria-label=\"Edit entry\"\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => handleDuplicate(entry)}\n disabled={duplicating}\n title=\"Duplicate entry\"\n aria-label=\"Duplicate entry\"\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-50\"\n >\n <Copy className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeleteEntry(entry)}\n title=\"Delete entry\"\n aria-label=\"Delete entry\"\n className=\"rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </GlassCard>\n\n <div className=\"mt-4 flex items-center justify-between\">\n <p className=\"text-xs text-text-secondary\">\n Showing {entries.length} of {total}\n </p>\n {hasMore && (\n <button\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n {loadingMore ? \"Loading...\" : \"Load more\"}\n </button>\n )}\n </div>\n </>\n )}\n\n {/* Create modal */}\n <Modal\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title=\"New Time Entry\"\n description=\"Manually log a block of work.\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => setCreateOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleCreate}\n disabled={creating || !createForm.startTime}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating ? \"Saving...\" : \"Create Entry\"}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to create entry. Please try again.\n </div>\n )}\n <EntryFormFields\n form={createForm}\n onChange={(patch) => setCreateForm((f) => ({ ...f, ...patch }))}\n projects={projects}\n />\n </Modal>\n\n {/* Edit modal */}\n <Modal\n open={Boolean(editEntry)}\n onClose={() => setEditEntry(null)}\n title=\"Edit Time Entry\"\n description=\"Correct the details of this logged block.\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => setEditEntry(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleUpdate}\n disabled={updating || !editForm.startTime}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {updating ? \"Saving...\" : \"Save Changes\"}\n </button>\n </>\n }\n >\n {updateError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to update entry. Please try again.\n </div>\n )}\n <EntryFormFields\n form={editForm}\n onChange={(patch) => setEditForm((f) => ({ ...f, ...patch }))}\n projects={projects}\n />\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={Boolean(deleteEntry)}\n onClose={() => setDeleteEntry(null)}\n title=\"Delete time entry?\"\n description=\"This permanently removes the entry. It cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setDeleteEntry(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-action-danger-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-danger-bg/90 disabled:opacity-50\"\n >\n {deleting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Delete the entry for{\" \"}\n <span className=\"font-medium text-text-primary\">\n {deleteEntry?.project?.name ??\n deleteEntry?.description ??\n \"this block of work\"}\n </span>\n ?\n </p>\n </Modal>\n\n {/* Filter modal */}\n <Modal\n open={filterOpen}\n onClose={() => setFilterOpen(false)}\n title=\"Filter entries\"\n description=\"Narrow the list to what you need.\"\n footer={\n <>\n <button\n onClick={clearFilter}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Clear\n </button>\n <button\n onClick={applyFilter}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n Apply\n </button>\n </>\n }\n >\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Search\n </label>\n <input\n type=\"text\"\n placeholder=\"Description contains...\"\n value={draftSearch}\n onChange={(e) => setDraftSearch(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Project\n </label>\n <select\n value={draftProjectId}\n onChange={(e) => setDraftProjectId(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"\">All Projects</option>\n {projects.map((project) => (\n <option key={project.id} value={project.id}>\n {project.name}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Billable\n </label>\n <select\n value={draftBillable}\n onChange={(e) =>\n setDraftBillable(e.target.value as \"all\" | \"yes\" | \"no\")\n }\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"all\">All</option>\n <option value=\"yes\">Billable only</option>\n <option value=\"no\">Non-billable only</option>\n </select>\n </div>\n </div>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -3,8 +3,8 @@ import { formatHours as t, formatTimerDuration as n, getInitials as ee } from ".
3
3
  import { StatCard as r } from "../../components/StatCard.js";
4
4
  import { LoadingSkeleton as i } from "../../components/LoadingSkeleton.js";
5
5
  import { GetTimeEntryStatsDocument as te, ListRunningTimersDocument as ne, ListTimeEntriesDocument as re, useGetTimeEntryStatsQuery as ie, useListProjectsQuery as ae, useListRunningTimersQuery as oe, useListTimeEntriesQuery as se, useStartTimerMutation as ce, useStopTimerMutation as le } from "../../../generated/wspace-operations.js";
6
- import { useTimeCampusStore as ue } from "../../store/timecampusStore.js";
7
6
  import { PageLayout as a } from "../../components/PageLayout.js";
7
+ import { useTimeCampusStore as ue } from "../../store/timecampusStore.js";
8
8
  import { StatusBadge as o } from "../../components/StatusBadge.js";
9
9
  import { EmptyState as s } from "../../components/EmptyState.js";
10
10
  import { Modal as de } from "../../components/Modal.js";
@@ -1 +1 @@
1
- {"version":3,"file":"TimeTrackingPage.js","names":[],"sources":["../../../../src/timecampus/pages/time/TimeTrackingPage.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { Clock, Play, Pause, Square } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { Modal } from \"../../components/Modal\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport {\n ChartCard,\n DonutChart,\n BarsChart,\n StatusBadge,\n} from \"../../components\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport {\n formatHours,\n formatTimerDuration,\n getInitials,\n} from \"../../utils/helpers\";\nimport { useTimeCampusStore } from \"../../store/timecampusStore\";\nimport {\n useGetTimeEntryStatsQuery,\n useListTimeEntriesQuery,\n useListRunningTimersQuery,\n useStartTimerMutation,\n useStopTimerMutation,\n useListProjectsQuery,\n ListRunningTimersDocument,\n ListTimeEntriesDocument,\n GetTimeEntryStatsDocument,\n type TimeEntryFieldsFragment,\n type ListProjectsQuery,\n} from \"@/generated/wspace-operations\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\n\ntype ProjectOption = ListProjectsQuery[\"projects\"][\"items\"][number];\n\nexport function TimeTrackingPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const { activeTimer, tickTimer, syncTimerFromServer } = useTimeCampusStore();\n const navigate = useTimeCampusNavigate();\n\n const [startOpen, setStartOpen] = useState(false);\n const [description, setDescription] = useState(\"\");\n const [projectId, setProjectId] = useState(\"\");\n const [billable, setBillable] = useState(false);\n\n const { data: statsData, loading: statsLoading } =\n useGetTimeEntryStatsQuery();\n const { data: entriesData, loading: entriesLoading } =\n useListTimeEntriesQuery({\n variables: { pagination: { limit: 10 } },\n });\n const {\n data: runningData,\n loading: runningLoading,\n refetch: refetchRunning,\n } = useListRunningTimersQuery({ fetchPolicy: \"cache-and-network\" });\n const { data: projectsData } = useListProjectsQuery({\n variables: { pagination: { limit: 50 } },\n });\n const projects: ProjectOption[] = projectsData?.projects?.items ?? [];\n\n const serverTimer = runningData?.runningTimers?.[0] ?? null;\n\n const refetchAll = [\n { query: ListRunningTimersDocument },\n {\n query: ListTimeEntriesDocument,\n variables: { pagination: { limit: 10 } },\n },\n { query: GetTimeEntryStatsDocument },\n ];\n\n const [startTimerMutation, { loading: starting, error: startError }] =\n useStartTimerMutation({ refetchQueries: refetchAll });\n const [stopTimerMutation, { loading: stopping }] = useStopTimerMutation({\n refetchQueries: refetchAll,\n });\n\n // Backend is authoritative for \"is a timer running\" — mirror into the store.\n useEffect(() => {\n if (serverTimer) {\n syncTimerFromServer({\n timeEntryId: serverTimer.timeEntry.id,\n elapsedSeconds: serverTimer.elapsedSeconds,\n projectId: serverTimer.timeEntry.projectId ?? undefined,\n taskId: serverTimer.timeEntry.taskId ?? undefined,\n description: serverTimer.timeEntry.description ?? undefined,\n });\n } else if (!runningLoading) {\n syncTimerFromServer(null);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [serverTimer?.timeEntry.id, serverTimer?.elapsedSeconds, runningLoading]);\n\n // Live seconds ticker for the display.\n useEffect(() => {\n if (!activeTimer?.isRunning) return;\n const interval = setInterval(() => tickTimer(), 1000);\n return () => clearInterval(interval);\n }, [activeTimer?.isRunning, tickTimer]);\n\n const isRunning = Boolean(serverTimer);\n\n const handleStart = async () => {\n await startTimerMutation({\n variables: {\n input: {\n description: description.trim() || undefined,\n projectId: projectId || undefined,\n billable,\n source: \"timecampus\",\n },\n },\n });\n setStartOpen(false);\n setDescription(\"\");\n setProjectId(\"\");\n setBillable(false);\n await refetchRunning();\n };\n\n const handleStop = async () => {\n if (!serverTimer) return;\n await stopTimerMutation({ variables: { id: serverTimer.timeEntry.id } });\n await refetchRunning();\n };\n\n const stats = statsData?.timeEntryStats;\n const todayEntries = entriesData?.timeEntries?.items ?? [];\n\n const noProjectLabel = t(\n \"timecampus.timeTracking.entries.noProject\",\n \"No Project\",\n );\n\n // Real aggregates for the charts — derived only from data already fetched.\n const billableHours = stats?.billableHours ?? 0;\n const totalHours = stats?.totalHours ?? 0;\n const nonBillableHours = Math.max(0, totalHours - billableHours);\n const billablePct =\n totalHours > 0 ? Math.round((billableHours / totalHours) * 100) : 0;\n\n const billableSlices = [\n {\n name: t(\"timecampus.timeTracking.charts.billable\", \"Billable\"),\n value: Number(billableHours.toFixed(2)),\n },\n {\n name: t(\"timecampus.timeTracking.charts.nonBillable\", \"Non-billable\"),\n value: Number(nonBillableHours.toFixed(2)),\n },\n ];\n\n const projectHours = new Map<string, number>();\n for (const entry of todayEntries) {\n const name = entry.project?.name ?? noProjectLabel;\n projectHours.set(\n name,\n (projectHours.get(name) ?? 0) + (entry.duration ?? 0) / 3600,\n );\n }\n const hoursByProject = Array.from(projectHours, ([name, hours]) => ({\n name,\n hours: Number(hours.toFixed(2)),\n }))\n .filter((row) => row.hours > 0)\n .sort((a, b) => b.hours - a.hours)\n .slice(0, 6);\n\n if (statsLoading) {\n return (\n <PageLayout\n title={t(\"timecampus.timeTracking.title\", \"Time Tracking\")}\n description={t(\n \"timecampus.timeTracking.description\",\n \"Track and manage your work hours\",\n )}\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={t(\"timecampus.timeTracking.title\", \"Time Tracking\")}\n description={t(\n \"timecampus.timeTracking.description\",\n \"Track and manage your work hours\",\n )}\n purpose={t(\n \"timecampus.timeTracking.purpose\",\n \"Start a timer the moment you begin work and your hours are captured automatically — review today's totals, see what's billable, and stop guessing where your day went.\",\n )}\n actions={\n <button\n onClick={() => (isRunning ? handleStop() : setStartOpen(true))}\n disabled={starting || stopping}\n className={`inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium disabled:opacity-50 ${isRunning ? \"bg-action-danger-bg text-action-primary-text hover:bg-action-danger-bg/90\" : \"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90\"}`}\n >\n {isRunning ? (\n <>\n <Square className=\"h-4 w-4\" />\n {t(\"timecampus.timeTracking.actions.stop\", \"Stop\")} (\n {formatTimerDuration(activeTimer?.seconds ?? 0)})\n </>\n ) : (\n <>\n <Play className=\"h-4 w-4\" />\n {t(\"timecampus.timeTracking.actions.startTimer\", \"Start Timer\")}\n </>\n )}\n </button>\n }\n >\n <GlassCard treatment=\"glass\" glow className=\"p-6 sm:p-8\">\n <div className=\"flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n <span\n className={`h-2 w-2 rounded-full ${isRunning ? \"bg-status-success-text motion-safe:animate-vc-glow-pulse\" : \"bg-muted-foreground\"}`}\n />\n {isRunning\n ? t(\"timecampus.timeTracking.hero.running\", \"Timer running\")\n : t(\"timecampus.timeTracking.hero.idle\", \"Timer idle\")}\n </div>\n <p className=\"mt-3 font-mono text-5xl font-bold tabular-nums text-text-primary sm:text-6xl\">\n {formatTimerDuration(activeTimer?.seconds ?? 0)}\n </p>\n <div className=\"mt-3 flex flex-wrap items-center gap-2 text-sm text-text-secondary\">\n <span className=\"min-w-0 truncate\">\n {activeTimer?.description ||\n (isRunning\n ? t(\n \"timecampus.timeTracking.entries.noDescription\",\n \"No description\",\n )\n : t(\n \"timecampus.timeTracking.hero.prompt\",\n \"Start a timer to begin tracking\",\n ))}\n </span>\n {serverTimer?.timeEntry.project?.name && (\n <StatusBadge\n label={serverTimer.timeEntry.project.name}\n variant=\"default\"\n />\n )}\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-3\">\n {isRunning ? (\n <button\n onClick={handleStop}\n disabled={stopping}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-danger-bg px-6 py-3 text-sm font-medium text-action-primary-text hover:bg-action-danger-bg/90 disabled:opacity-50\"\n >\n <Square className=\"h-4 w-4\" />\n {stopping\n ? t(\"timecampus.timeTracking.hero.stopping\", \"Stopping...\")\n : t(\"timecampus.timeTracking.actions.stop\", \"Stop\")}\n </button>\n ) : (\n <button\n onClick={() => setStartOpen(true)}\n disabled={starting}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-6 py-3 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n <Play className=\"h-4 w-4\" />\n {t(\"timecampus.timeTracking.actions.startTimer\", \"Start Timer\")}\n </button>\n )}\n </div>\n </div>\n </GlassCard>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title={t(\"timecampus.timeTracking.stats.today\", \"Today\")}\n value={formatHours(totalHours)}\n icon={<Clock className=\"h-5 w-5\" />}\n description={`${billablePct}% ${t(\"timecampus.timeTracking.stats.billableSuffix\", \"billable\")}`}\n />\n <StatCard\n title={t(\"timecampus.timeTracking.stats.billable\", \"Billable\")}\n value={formatHours(stats?.billableHours ?? 0)}\n icon={<Clock className=\"h-5 w-5\" />}\n description={`${stats?.totalEntries ?? 0} ${t(\"timecampus.timeTracking.stats.entries\", \"entries\")}`}\n />\n <StatCard\n title={t(\n \"timecampus.timeTracking.stats.activeTimers\",\n \"Active Timers\",\n )}\n value={stats?.activeTimers ?? 0}\n icon={<Clock className=\"h-5 w-5\" />}\n description={t(\n \"timecampus.timeTracking.stats.currentlyRunning\",\n \"Currently running\",\n )}\n />\n <StatCard\n title={t(\"timecampus.timeTracking.stats.activeTimer\", \"Active Timer\")}\n value={\n activeTimer\n ? formatTimerDuration(activeTimer.seconds)\n : t(\"timecampus.timeTracking.stats.stopped\", \"Stopped\")\n }\n icon={\n activeTimer ? (\n <Pause className=\"h-5 w-5\" />\n ) : (\n <Play className=\"h-5 w-5\" />\n )\n }\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <ChartCard\n title={t(\n \"timecampus.timeTracking.charts.billableTitle\",\n \"Billable mix\",\n )}\n description={t(\n \"timecampus.timeTracking.charts.billableDesc\",\n \"Billable vs non-billable hours\",\n )}\n >\n <DonutChart\n data={billableSlices}\n centerValue={`${billablePct}%`}\n centerLabel={t(\n \"timecampus.timeTracking.charts.billableCenter\",\n \"billable\",\n )}\n valueFormat={(value) => formatHours(value)}\n emptyLabel={t(\n \"timecampus.timeTracking.charts.noHours\",\n \"No hours tracked yet\",\n )}\n height={240}\n />\n </ChartCard>\n <ChartCard\n title={t(\n \"timecampus.timeTracking.charts.projectTitle\",\n \"Hours by project\",\n )}\n description={t(\n \"timecampus.timeTracking.charts.projectDesc\",\n \"Where your recent tracked time went\",\n )}\n >\n <BarsChart\n data={hoursByProject}\n xKey=\"name\"\n series={[\n {\n key: \"hours\",\n label: t(\"timecampus.timeTracking.charts.hours\", \"Hours\"),\n },\n ]}\n orientation=\"horizontal\"\n valueFormat={(value) => formatHours(value)}\n categoryWidth={110}\n emptyLabel={t(\n \"timecampus.timeTracking.charts.noProjectHours\",\n \"No project time yet\",\n )}\n height={240}\n />\n </ChartCard>\n </div>\n\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {t(\"timecampus.timeTracking.entries.title\", \"Today's Entries\")}\n </h3>\n <button\n onClick={() => navigate(\"/time/entries\")}\n className=\"text-sm font-medium text-primary hover:underline\"\n >\n {t(\"timecampus.timeTracking.entries.viewAll\", \"View all\")}\n </button>\n </div>\n {entriesLoading ? (\n <LoadingSkeleton variant=\"list\" count={4} />\n ) : todayEntries.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={t(\n \"timecampus.timeTracking.entries.empty.title\",\n \"No time entries yet\",\n )}\n description={t(\n \"timecampus.timeTracking.entries.empty\",\n \"No time entries yet. Start a timer to begin tracking.\",\n )}\n action={\n <button\n onClick={() => setStartOpen(true)}\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\"\n >\n <Play className=\"h-4 w-4\" />\n {t(\"timecampus.timeTracking.actions.startTimer\", \"Start Timer\")}\n </button>\n }\n />\n ) : (\n <div className=\"flex flex-col gap-2\">\n {todayEntries.map((entry: TimeEntryFieldsFragment) => (\n <div\n key={entry.id}\n className=\"flex items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface p-3 transition-colors hover:bg-bg-sunken/50\"\n >\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-xs font-semibold text-primary\">\n {getInitials(entry.project?.name ?? noProjectLabel)}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <p className=\"min-w-0 truncate text-sm font-medium text-text-primary\">\n {entry.project?.name ?? noProjectLabel}\n </p>\n {entry.billable && (\n <StatusBadge\n label={t(\n \"timecampus.timeTracking.charts.billable\",\n \"Billable\",\n )}\n variant=\"success\"\n className=\"shrink-0\"\n />\n )}\n </div>\n <p className=\"truncate text-xs text-text-secondary\">\n {entry.task?.title ??\n entry.description ??\n t(\n \"timecampus.timeTracking.entries.noDescription\",\n \"No description\",\n )}\n </p>\n </div>\n <div className=\"flex shrink-0 flex-col items-end gap-1\">\n <p className=\"font-mono text-sm font-medium text-text-primary\">\n {formatHours((entry.duration ?? 0) / 3600)}\n </p>\n <StatusBadge\n status={entry.isRunning ? \"running\" : \"completed\"}\n dot={entry.isRunning}\n label={\n entry.isRunning\n ? t(\n \"timecampus.timeTracking.entries.status.running\",\n \"running\",\n )\n : t(\n \"timecampus.timeTracking.entries.status.completed\",\n \"completed\",\n )\n }\n />\n </div>\n </div>\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Start timer modal */}\n <Modal\n open={startOpen}\n onClose={() => setStartOpen(false)}\n title={t(\"timecampus.timeTracking.startModal.title\", \"Start a timer\")}\n description={t(\n \"timecampus.timeTracking.startModal.description\",\n \"Track the task you're working on right now.\",\n )}\n footer={\n <>\n <button\n onClick={() => setStartOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleStart}\n disabled={starting}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {starting\n ? t(\n \"timecampus.timeTracking.startModal.starting\",\n \"Starting...\",\n )\n : t(\n \"timecampus.timeTracking.actions.startTimer\",\n \"Start Timer\",\n )}\n </button>\n </>\n }\n >\n {startError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.timeTracking.startModal.error\",\n \"Failed to start the timer. Please try again.\",\n )}\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t(\n \"timecampus.timeTracking.startModal.descriptionLabel\",\n \"What are you working on?\",\n )}\n </label>\n <input\n type=\"text\"\n placeholder={t(\n \"timecampus.timeTracking.startModal.descriptionPlaceholder\",\n \"Description\",\n )}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t(\"timecampus.timeTracking.startModal.projectLabel\", \"Project\")}\n </label>\n <select\n value={projectId}\n onChange={(e) => setProjectId(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"\">\n {t(\"timecampus.timeTracking.entries.noProject\", \"No Project\")}\n </option>\n {projects.map((project) => (\n <option key={project.id} value={project.id}>\n {project.name}\n </option>\n ))}\n </select>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={billable}\n onChange={(e) => setBillable(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n {t(\"timecampus.timeTracking.startModal.billable\", \"Billable\")}\n </label>\n </div>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TimeTrackingPage.js","names":[],"sources":["../../../../src/timecampus/pages/time/TimeTrackingPage.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { Clock, Play, Pause, Square } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { Modal } from \"../../components/Modal\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport {\n ChartCard,\n DonutChart,\n BarsChart,\n StatusBadge,\n} from \"../../components\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport {\n formatHours,\n formatTimerDuration,\n getInitials,\n} from \"../../utils/helpers\";\nimport { useTimeCampusStore } from \"../../store/timecampusStore\";\nimport {\n useGetTimeEntryStatsQuery,\n useListTimeEntriesQuery,\n useListRunningTimersQuery,\n useStartTimerMutation,\n useStopTimerMutation,\n useListProjectsQuery,\n ListRunningTimersDocument,\n ListTimeEntriesDocument,\n GetTimeEntryStatsDocument,\n type TimeEntryFieldsFragment,\n type ListProjectsQuery,\n} from \"@/generated/wspace-operations\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\n\ntype ProjectOption = ListProjectsQuery[\"projects\"][\"items\"][number];\n\nexport function TimeTrackingPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const { activeTimer, tickTimer, syncTimerFromServer } = useTimeCampusStore();\n const navigate = useTimeCampusNavigate();\n\n const [startOpen, setStartOpen] = useState(false);\n const [description, setDescription] = useState(\"\");\n const [projectId, setProjectId] = useState(\"\");\n const [billable, setBillable] = useState(false);\n\n const { data: statsData, loading: statsLoading } =\n useGetTimeEntryStatsQuery();\n const { data: entriesData, loading: entriesLoading } =\n useListTimeEntriesQuery({\n variables: { pagination: { limit: 10 } },\n });\n const {\n data: runningData,\n loading: runningLoading,\n refetch: refetchRunning,\n } = useListRunningTimersQuery({ fetchPolicy: \"cache-and-network\" });\n const { data: projectsData } = useListProjectsQuery({\n variables: { pagination: { limit: 50 } },\n });\n const projects: ProjectOption[] = projectsData?.projects?.items ?? [];\n\n const serverTimer = runningData?.runningTimers?.[0] ?? null;\n\n const refetchAll = [\n { query: ListRunningTimersDocument },\n {\n query: ListTimeEntriesDocument,\n variables: { pagination: { limit: 10 } },\n },\n { query: GetTimeEntryStatsDocument },\n ];\n\n const [startTimerMutation, { loading: starting, error: startError }] =\n useStartTimerMutation({ refetchQueries: refetchAll });\n const [stopTimerMutation, { loading: stopping }] = useStopTimerMutation({\n refetchQueries: refetchAll,\n });\n\n // Backend is authoritative for \"is a timer running\" — mirror into the store.\n useEffect(() => {\n if (serverTimer) {\n syncTimerFromServer({\n timeEntryId: serverTimer.timeEntry.id,\n elapsedSeconds: serverTimer.elapsedSeconds,\n projectId: serverTimer.timeEntry.projectId ?? undefined,\n taskId: serverTimer.timeEntry.taskId ?? undefined,\n description: serverTimer.timeEntry.description ?? undefined,\n });\n } else if (!runningLoading) {\n syncTimerFromServer(null);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [serverTimer?.timeEntry.id, serverTimer?.elapsedSeconds, runningLoading]);\n\n // Live seconds ticker for the display.\n useEffect(() => {\n if (!activeTimer?.isRunning) return;\n const interval = setInterval(() => tickTimer(), 1000);\n return () => clearInterval(interval);\n }, [activeTimer?.isRunning, tickTimer]);\n\n const isRunning = Boolean(serverTimer);\n\n const handleStart = async () => {\n await startTimerMutation({\n variables: {\n input: {\n description: description.trim() || undefined,\n projectId: projectId || undefined,\n billable,\n source: \"timecampus\",\n },\n },\n });\n setStartOpen(false);\n setDescription(\"\");\n setProjectId(\"\");\n setBillable(false);\n await refetchRunning();\n };\n\n const handleStop = async () => {\n if (!serverTimer) return;\n await stopTimerMutation({ variables: { id: serverTimer.timeEntry.id } });\n await refetchRunning();\n };\n\n const stats = statsData?.timeEntryStats;\n const todayEntries = entriesData?.timeEntries?.items ?? [];\n\n const noProjectLabel = t(\n \"timecampus.timeTracking.entries.noProject\",\n \"No Project\",\n );\n\n // Real aggregates for the charts — derived only from data already fetched.\n const billableHours = stats?.billableHours ?? 0;\n const totalHours = stats?.totalHours ?? 0;\n const nonBillableHours = Math.max(0, totalHours - billableHours);\n const billablePct =\n totalHours > 0 ? Math.round((billableHours / totalHours) * 100) : 0;\n\n const billableSlices = [\n {\n name: t(\"timecampus.timeTracking.charts.billable\", \"Billable\"),\n value: Number(billableHours.toFixed(2)),\n },\n {\n name: t(\"timecampus.timeTracking.charts.nonBillable\", \"Non-billable\"),\n value: Number(nonBillableHours.toFixed(2)),\n },\n ];\n\n const projectHours = new Map<string, number>();\n for (const entry of todayEntries) {\n const name = entry.project?.name ?? noProjectLabel;\n projectHours.set(\n name,\n (projectHours.get(name) ?? 0) + (entry.duration ?? 0) / 3600,\n );\n }\n const hoursByProject = Array.from(projectHours, ([name, hours]) => ({\n name,\n hours: Number(hours.toFixed(2)),\n }))\n .filter((row) => row.hours > 0)\n .sort((a, b) => b.hours - a.hours)\n .slice(0, 6);\n\n if (statsLoading) {\n return (\n <PageLayout\n title={t(\"timecampus.timeTracking.title\", \"Time Tracking\")}\n description={t(\n \"timecampus.timeTracking.description\",\n \"Track and manage your work hours\",\n )}\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={t(\"timecampus.timeTracking.title\", \"Time Tracking\")}\n description={t(\n \"timecampus.timeTracking.description\",\n \"Track and manage your work hours\",\n )}\n purpose={t(\n \"timecampus.timeTracking.purpose\",\n \"Start a timer the moment you begin work and your hours are captured automatically — review today's totals, see what's billable, and stop guessing where your day went.\",\n )}\n actions={\n <button\n onClick={() => (isRunning ? handleStop() : setStartOpen(true))}\n disabled={starting || stopping}\n className={`inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium disabled:opacity-50 ${isRunning ? \"bg-action-danger-bg text-action-primary-text hover:bg-action-danger-bg/90\" : \"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90\"}`}\n >\n {isRunning ? (\n <>\n <Square className=\"h-4 w-4\" />\n {t(\"timecampus.timeTracking.actions.stop\", \"Stop\")} (\n {formatTimerDuration(activeTimer?.seconds ?? 0)})\n </>\n ) : (\n <>\n <Play className=\"h-4 w-4\" />\n {t(\"timecampus.timeTracking.actions.startTimer\", \"Start Timer\")}\n </>\n )}\n </button>\n }\n >\n <GlassCard treatment=\"glass\" glow className=\"p-6 sm:p-8\">\n <div className=\"flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2 text-xs font-medium uppercase tracking-wide text-text-secondary\">\n <span\n className={`h-2 w-2 rounded-full ${isRunning ? \"bg-status-success-text motion-safe:animate-vc-glow-pulse\" : \"bg-muted-foreground\"}`}\n />\n {isRunning\n ? t(\"timecampus.timeTracking.hero.running\", \"Timer running\")\n : t(\"timecampus.timeTracking.hero.idle\", \"Timer idle\")}\n </div>\n <p className=\"mt-3 font-mono text-5xl font-bold tabular-nums text-text-primary sm:text-6xl\">\n {formatTimerDuration(activeTimer?.seconds ?? 0)}\n </p>\n <div className=\"mt-3 flex flex-wrap items-center gap-2 text-sm text-text-secondary\">\n <span className=\"min-w-0 truncate\">\n {activeTimer?.description ||\n (isRunning\n ? t(\n \"timecampus.timeTracking.entries.noDescription\",\n \"No description\",\n )\n : t(\n \"timecampus.timeTracking.hero.prompt\",\n \"Start a timer to begin tracking\",\n ))}\n </span>\n {serverTimer?.timeEntry.project?.name && (\n <StatusBadge\n label={serverTimer.timeEntry.project.name}\n variant=\"default\"\n />\n )}\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-3\">\n {isRunning ? (\n <button\n onClick={handleStop}\n disabled={stopping}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-danger-bg px-6 py-3 text-sm font-medium text-action-primary-text hover:bg-action-danger-bg/90 disabled:opacity-50\"\n >\n <Square className=\"h-4 w-4\" />\n {stopping\n ? t(\"timecampus.timeTracking.hero.stopping\", \"Stopping...\")\n : t(\"timecampus.timeTracking.actions.stop\", \"Stop\")}\n </button>\n ) : (\n <button\n onClick={() => setStartOpen(true)}\n disabled={starting}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-6 py-3 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n <Play className=\"h-4 w-4\" />\n {t(\"timecampus.timeTracking.actions.startTimer\", \"Start Timer\")}\n </button>\n )}\n </div>\n </div>\n </GlassCard>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title={t(\"timecampus.timeTracking.stats.today\", \"Today\")}\n value={formatHours(totalHours)}\n icon={<Clock className=\"h-5 w-5\" />}\n description={`${billablePct}% ${t(\"timecampus.timeTracking.stats.billableSuffix\", \"billable\")}`}\n />\n <StatCard\n title={t(\"timecampus.timeTracking.stats.billable\", \"Billable\")}\n value={formatHours(stats?.billableHours ?? 0)}\n icon={<Clock className=\"h-5 w-5\" />}\n description={`${stats?.totalEntries ?? 0} ${t(\"timecampus.timeTracking.stats.entries\", \"entries\")}`}\n />\n <StatCard\n title={t(\n \"timecampus.timeTracking.stats.activeTimers\",\n \"Active Timers\",\n )}\n value={stats?.activeTimers ?? 0}\n icon={<Clock className=\"h-5 w-5\" />}\n description={t(\n \"timecampus.timeTracking.stats.currentlyRunning\",\n \"Currently running\",\n )}\n />\n <StatCard\n title={t(\"timecampus.timeTracking.stats.activeTimer\", \"Active Timer\")}\n value={\n activeTimer\n ? formatTimerDuration(activeTimer.seconds)\n : t(\"timecampus.timeTracking.stats.stopped\", \"Stopped\")\n }\n icon={\n activeTimer ? (\n <Pause className=\"h-5 w-5\" />\n ) : (\n <Play className=\"h-5 w-5\" />\n )\n }\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <ChartCard\n title={t(\n \"timecampus.timeTracking.charts.billableTitle\",\n \"Billable mix\",\n )}\n description={t(\n \"timecampus.timeTracking.charts.billableDesc\",\n \"Billable vs non-billable hours\",\n )}\n >\n <DonutChart\n data={billableSlices}\n centerValue={`${billablePct}%`}\n centerLabel={t(\n \"timecampus.timeTracking.charts.billableCenter\",\n \"billable\",\n )}\n valueFormat={(value) => formatHours(value)}\n emptyLabel={t(\n \"timecampus.timeTracking.charts.noHours\",\n \"No hours tracked yet\",\n )}\n height={240}\n />\n </ChartCard>\n <ChartCard\n title={t(\n \"timecampus.timeTracking.charts.projectTitle\",\n \"Hours by project\",\n )}\n description={t(\n \"timecampus.timeTracking.charts.projectDesc\",\n \"Where your recent tracked time went\",\n )}\n >\n <BarsChart\n data={hoursByProject}\n xKey=\"name\"\n series={[\n {\n key: \"hours\",\n label: t(\"timecampus.timeTracking.charts.hours\", \"Hours\"),\n },\n ]}\n orientation=\"horizontal\"\n valueFormat={(value) => formatHours(value)}\n categoryWidth={110}\n emptyLabel={t(\n \"timecampus.timeTracking.charts.noProjectHours\",\n \"No project time yet\",\n )}\n height={240}\n />\n </ChartCard>\n </div>\n\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {t(\"timecampus.timeTracking.entries.title\", \"Today's Entries\")}\n </h3>\n <button\n onClick={() => navigate(\"/time/entries\")}\n className=\"text-sm font-medium text-primary hover:underline\"\n >\n {t(\"timecampus.timeTracking.entries.viewAll\", \"View all\")}\n </button>\n </div>\n {entriesLoading ? (\n <LoadingSkeleton variant=\"list\" count={4} />\n ) : todayEntries.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={t(\n \"timecampus.timeTracking.entries.empty.title\",\n \"No time entries yet\",\n )}\n description={t(\n \"timecampus.timeTracking.entries.empty\",\n \"No time entries yet. Start a timer to begin tracking.\",\n )}\n action={\n <button\n onClick={() => setStartOpen(true)}\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\"\n >\n <Play className=\"h-4 w-4\" />\n {t(\"timecampus.timeTracking.actions.startTimer\", \"Start Timer\")}\n </button>\n }\n />\n ) : (\n <div className=\"flex flex-col gap-2\">\n {todayEntries.map((entry: TimeEntryFieldsFragment) => (\n <div\n key={entry.id}\n className=\"flex items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface p-3 transition-colors hover:bg-bg-sunken/50\"\n >\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-xs font-semibold text-primary\">\n {getInitials(entry.project?.name ?? noProjectLabel)}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <p className=\"min-w-0 truncate text-sm font-medium text-text-primary\">\n {entry.project?.name ?? noProjectLabel}\n </p>\n {entry.billable && (\n <StatusBadge\n label={t(\n \"timecampus.timeTracking.charts.billable\",\n \"Billable\",\n )}\n variant=\"success\"\n className=\"shrink-0\"\n />\n )}\n </div>\n <p className=\"truncate text-xs text-text-secondary\">\n {entry.task?.title ??\n entry.description ??\n t(\n \"timecampus.timeTracking.entries.noDescription\",\n \"No description\",\n )}\n </p>\n </div>\n <div className=\"flex shrink-0 flex-col items-end gap-1\">\n <p className=\"font-mono text-sm font-medium text-text-primary\">\n {formatHours((entry.duration ?? 0) / 3600)}\n </p>\n <StatusBadge\n status={entry.isRunning ? \"running\" : \"completed\"}\n dot={entry.isRunning}\n label={\n entry.isRunning\n ? t(\n \"timecampus.timeTracking.entries.status.running\",\n \"running\",\n )\n : t(\n \"timecampus.timeTracking.entries.status.completed\",\n \"completed\",\n )\n }\n />\n </div>\n </div>\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Start timer modal */}\n <Modal\n open={startOpen}\n onClose={() => setStartOpen(false)}\n title={t(\"timecampus.timeTracking.startModal.title\", \"Start a timer\")}\n description={t(\n \"timecampus.timeTracking.startModal.description\",\n \"Track the task you're working on right now.\",\n )}\n footer={\n <>\n <button\n onClick={() => setStartOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleStart}\n disabled={starting}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {starting\n ? t(\n \"timecampus.timeTracking.startModal.starting\",\n \"Starting...\",\n )\n : t(\n \"timecampus.timeTracking.actions.startTimer\",\n \"Start Timer\",\n )}\n </button>\n </>\n }\n >\n {startError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.timeTracking.startModal.error\",\n \"Failed to start the timer. Please try again.\",\n )}\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t(\n \"timecampus.timeTracking.startModal.descriptionLabel\",\n \"What are you working on?\",\n )}\n </label>\n <input\n type=\"text\"\n placeholder={t(\n \"timecampus.timeTracking.startModal.descriptionPlaceholder\",\n \"Description\",\n )}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t(\"timecampus.timeTracking.startModal.projectLabel\", \"Project\")}\n </label>\n <select\n value={projectId}\n onChange={(e) => setProjectId(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"\">\n {t(\"timecampus.timeTracking.entries.noProject\", \"No Project\")}\n </option>\n {projects.map((project) => (\n <option key={project.id} value={project.id}>\n {project.name}\n </option>\n ))}\n </select>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={billable}\n onChange={(e) => setBillable(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n {t(\"timecampus.timeTracking.startModal.billable\", \"Billable\")}\n </label>\n </div>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -2,8 +2,8 @@ import { useTimeCampusNavigate as e } from "../../providers/TimeCampusProvider.j
2
2
  import { formatTimerDuration as t, getInitials as ee } from "../../utils/helpers.js";
3
3
  import { LoadingSkeleton as n } from "../../components/LoadingSkeleton.js";
4
4
  import { GetTimeEntryStatsDocument as te, ListRunningTimersDocument as r, ListTimeEntriesDocument as i, useDeleteTimeEntryMutation as ne, useListProjectsQuery as a, useListRunningTimersQuery as o, useStartTimerMutation as s, useStopTimerMutation as c } from "../../../generated/wspace-operations.js";
5
- import { useTimeCampusStore as l } from "../../store/timecampusStore.js";
6
- import { PageLayout as u } from "../../components/PageLayout.js";
5
+ import { PageLayout as l } from "../../components/PageLayout.js";
6
+ import { useTimeCampusStore as u } from "../../store/timecampusStore.js";
7
7
  import { StatusBadge as d } from "../../components/StatusBadge.js";
8
8
  import { EmptyState as f } from "../../components/EmptyState.js";
9
9
  import { Modal as p } from "../../components/Modal.js";
@@ -13,7 +13,7 @@ import { Fragment as y, jsx as b, jsxs as x } from "react/jsx-runtime";
13
13
  import { GlassCard as S, NextSteps as C } from "@burdenoff/fe-libs/ui";
14
14
  //#region src/timecampus/pages/time/TimerPage.tsx
15
15
  function w() {
16
- let { activeTimer: w, tickTimer: T, syncTimerFromServer: E } = l(), D = e(), [O, k] = h(!1), [A, j] = h(""), [M, N] = h(""), [P, F] = h(!1), [I, L] = h(null), { data: R, loading: z, refetch: B } = o({ fetchPolicy: "cache-and-network" }), { data: V } = a({ variables: { pagination: { limit: 50 } } }), H = V?.projects?.items ?? [], U = R?.runningTimers ?? [], W = U[0] ?? null, G = [
16
+ let { activeTimer: w, tickTimer: T, syncTimerFromServer: E } = u(), D = e(), [O, k] = h(!1), [A, j] = h(""), [M, N] = h(""), [P, F] = h(!1), [I, L] = h(null), { data: R, loading: z, refetch: B } = o({ fetchPolicy: "cache-and-network" }), { data: V } = a({ variables: { pagination: { limit: 50 } } }), H = V?.projects?.items ?? [], U = R?.runningTimers ?? [], W = U[0] ?? null, G = [
17
17
  { query: r },
18
18
  {
19
19
  query: i,
@@ -50,7 +50,7 @@ function w() {
50
50
  }, ae = async () => {
51
51
  I && (await Y({ variables: { id: I.timeEntry.id } }), await re({ variables: { id: I.timeEntry.id } }), L(null), await B());
52
52
  }, $ = !!W;
53
- return /* @__PURE__ */ x(u, {
53
+ return /* @__PURE__ */ x(l, {
54
54
  title: "Timer",
55
55
  description: "Focus timer for deep work sessions",
56
56
  purpose: "A single, distraction-free stopwatch for the task in front of you — start it when you begin focused work and it logs the session as a time entry you can attribute to a project later.",