@burdenoff/microfe-timecampus 2026.624.1 → 2026.626.1

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 (109) hide show
  1. package/dist/timecampus/components/EmptyState.js +14 -0
  2. package/dist/timecampus/components/EmptyState.js.map +1 -0
  3. package/dist/timecampus/components/ErrorBoundary.js +2 -2
  4. package/dist/timecampus/components/ErrorBoundary.js.map +1 -1
  5. package/dist/timecampus/components/LoadingSkeleton.js +1 -1
  6. package/dist/timecampus/components/LoadingSkeleton.js.map +1 -1
  7. package/dist/timecampus/components/PageLayout.js +26 -14
  8. package/dist/timecampus/components/PageLayout.js.map +1 -1
  9. package/dist/timecampus/components/StatCard.js +19 -16
  10. package/dist/timecampus/components/StatCard.js.map +1 -1
  11. package/dist/timecampus/pages/HomePage.js +169 -149
  12. package/dist/timecampus/pages/HomePage.js.map +1 -1
  13. package/dist/timecampus/pages/applets/AppletStorePage.js +38 -35
  14. package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
  15. package/dist/timecampus/pages/applets/InstalledAppletsPage.js +42 -36
  16. package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
  17. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +57 -53
  18. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
  19. package/dist/timecampus/pages/application-usage/ApplicationUsageSettingsPage.js +40 -36
  20. package/dist/timecampus/pages/application-usage/ApplicationUsageSettingsPage.js.map +1 -1
  21. package/dist/timecampus/pages/channels/ChannelsPage.js +32 -29
  22. package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
  23. package/dist/timecampus/pages/client-portal/ClientPortalPage.js +52 -44
  24. package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
  25. package/dist/timecampus/pages/dashboard/AnalyticsPage.js +65 -38
  26. package/dist/timecampus/pages/dashboard/AnalyticsPage.js.map +1 -1
  27. package/dist/timecampus/pages/dashboard/ReportsPage.js +47 -41
  28. package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
  29. package/dist/timecampus/pages/email-tracking/EmailAnalyticsPage.js +16 -14
  30. package/dist/timecampus/pages/email-tracking/EmailAnalyticsPage.js.map +1 -1
  31. package/dist/timecampus/pages/email-tracking/EmailSettingsPage.js +40 -36
  32. package/dist/timecampus/pages/email-tracking/EmailSettingsPage.js.map +1 -1
  33. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +34 -28
  34. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
  35. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +49 -41
  36. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
  37. package/dist/timecampus/pages/focus-score/FocusScorePage.js +66 -62
  38. package/dist/timecampus/pages/focus-score/FocusScorePage.js.map +1 -1
  39. package/dist/timecampus/pages/focus-score/FocusScoreSettingsPage.js +37 -33
  40. package/dist/timecampus/pages/focus-score/FocusScoreSettingsPage.js.map +1 -1
  41. package/dist/timecampus/pages/geofencing/GeofencingPage.js +37 -30
  42. package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
  43. package/dist/timecampus/pages/geofencing/GeofencingSettingsPage.js +35 -31
  44. package/dist/timecampus/pages/geofencing/GeofencingSettingsPage.js.map +1 -1
  45. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +37 -34
  46. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
  47. package/dist/timecampus/pages/location-tracking/LocationPrivacySettingsPage.js +35 -31
  48. package/dist/timecampus/pages/location-tracking/LocationPrivacySettingsPage.js.map +1 -1
  49. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +45 -41
  50. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
  51. package/dist/timecampus/pages/meetings/MeetingAnalyticsPage.js +17 -15
  52. package/dist/timecampus/pages/meetings/MeetingAnalyticsPage.js.map +1 -1
  53. package/dist/timecampus/pages/meetings/MeetingCreatePage.js +36 -32
  54. package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
  55. package/dist/timecampus/pages/meetings/MeetingDetailPage.js +120 -102
  56. package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
  57. package/dist/timecampus/pages/meetings/MeetingsPage.js +46 -36
  58. package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
  59. package/dist/timecampus/pages/pomodoro/PomodoroAnalyticsPage.js +19 -17
  60. package/dist/timecampus/pages/pomodoro/PomodoroAnalyticsPage.js.map +1 -1
  61. package/dist/timecampus/pages/pomodoro/PomodoroPage.js +143 -112
  62. package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
  63. package/dist/timecampus/pages/pomodoro/PomodoroSettingsPage.js +55 -51
  64. package/dist/timecampus/pages/pomodoro/PomodoroSettingsPage.js.map +1 -1
  65. package/dist/timecampus/pages/projects/ProjectCreatePage.js +32 -28
  66. package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
  67. package/dist/timecampus/pages/projects/ProjectDetailPage.js +107 -76
  68. package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
  69. package/dist/timecampus/pages/projects/ProjectsPage.js +21 -17
  70. package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
  71. package/dist/timecampus/pages/projects/TasksPage.js +44 -34
  72. package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
  73. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +59 -55
  74. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
  75. package/dist/timecampus/pages/team/TeamAnalyticsPage.js +24 -20
  76. package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
  77. package/dist/timecampus/pages/team/TeamMembersPage.js +47 -41
  78. package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
  79. package/dist/timecampus/pages/team/TeamPage.js +89 -59
  80. package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
  81. package/dist/timecampus/pages/team-activity/TeamActivityPage.js +47 -43
  82. package/dist/timecampus/pages/team-activity/TeamActivityPage.js.map +1 -1
  83. package/dist/timecampus/pages/team-activity/TeamActivitySettingsPage.js +39 -35
  84. package/dist/timecampus/pages/team-activity/TeamActivitySettingsPage.js.map +1 -1
  85. package/dist/timecampus/pages/templates/TemplateApplicationPage.js +24 -20
  86. package/dist/timecampus/pages/templates/TemplateApplicationPage.js.map +1 -1
  87. package/dist/timecampus/pages/templates/TemplateDetailPage.js +66 -48
  88. package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
  89. package/dist/timecampus/pages/templates/TemplatesPage.js +39 -36
  90. package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
  91. package/dist/timecampus/pages/time/TimeEntriesPage.js +53 -44
  92. package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
  93. package/dist/timecampus/pages/time/TimeTrackingPage.js +72 -71
  94. package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
  95. package/dist/timecampus/pages/time/TimerPage.js +100 -66
  96. package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
  97. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +63 -59
  98. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
  99. package/dist/timecampus/pages/widgets/WidgetDetailPage.js +64 -46
  100. package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
  101. package/dist/timecampus/pages/widgets/WidgetsPage.js +44 -37
  102. package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
  103. package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +18 -16
  104. package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
  105. package/dist/timecampus/pages/work-patterns/WorkPatternsSettingsPage.js +40 -36
  106. package/dist/timecampus/pages/work-patterns/WorkPatternsSettingsPage.js.map +1 -1
  107. package/dist/timecampus/pages/workflows/WorkflowsPage.js +47 -37
  108. package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
  109. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"TimeTrackingPage.js","names":[],"sources":["../../../../src/timecampus/pages/time/TimeTrackingPage.tsx"],"sourcesContent":["import { Clock, Play, Pause, Square } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { formatHours, formatTimerDuration } from \"../../utils/helpers\";\nimport { useTimeCampusStore } from \"../../store/timecampusStore\";\nimport {\n useGetTimeEntryStatsQuery,\n useListTimeEntriesQuery,\n type TimeEntryFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\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, startTimer, stopTimer } = useTimeCampusStore();\n\n const { data: statsData, loading: statsLoading } =\n useGetTimeEntryStatsQuery();\n const { data: entriesData, loading: entriesLoading } =\n useListTimeEntriesQuery({\n variables: { limit: 10 },\n });\n\n const stats = statsData?.timeEntryStats;\n const todayEntries = entriesData?.timeEntries?.items ?? [];\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 actions={\n <button\n onClick={() => (activeTimer ? stopTimer() : startTimer())}\n className={`inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium ${activeTimer ? \"bg-red-500 text-white hover:bg-red-600\" : \"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90\"}`}\n >\n {activeTimer ? (\n <>\n <Square className=\"h-4 w-4\" />\n {t(\"timecampus.timeTracking.actions.stop\", \"Stop\")} (\n {formatTimerDuration(activeTimer.seconds)})\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 <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n title={t(\"timecampus.timeTracking.stats.today\", \"Today\")}\n value={formatHours(stats?.totalHours ?? 0)}\n icon={<Clock className=\"h-5 w-5\" />}\n trend={{\n value: 15,\n label: t(\n \"timecampus.timeTracking.stats.vsYesterday\",\n \"vs yesterday\",\n ),\n }}\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=\"rounded-xl border border-border-subtle bg-bg-surface p-6\">\n <h3 className=\"mb-4 text-sm font-semibold text-text-primary\">\n {t(\"timecampus.timeTracking.entries.title\", \"Today's Entries\")}\n </h3>\n {entriesLoading ? (\n <LoadingSkeleton variant=\"list\" count={4} />\n ) : todayEntries.length === 0 ? (\n <div className=\"flex h-48 items-center justify-center rounded-lg border border-dashed border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n {t(\n \"timecampus.timeTracking.entries.empty\",\n \"No time entries yet. Start a timer to begin tracking.\",\n )}\n </p>\n </div>\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 justify-between rounded-lg border border-border-subtle p-3 transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n {entry.project?.name ??\n t(\n \"timecampus.timeTracking.entries.noProject\",\n \"No Project\",\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {entry.description ??\n t(\n \"timecampus.timeTracking.entries.noDescription\",\n \"No description\",\n )}\n </p>\n </div>\n <div className=\"ml-4 text-right\">\n <p className=\"text-sm font-mono font-medium text-text-primary\">\n {formatHours((entry.duration ?? 0) / 3600)}\n </p>\n <span className=\"inline-flex items-center rounded-full bg-emerald-100 px-2 py-0.5 text-xs font-medium text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-400\">\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 </span>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;AAaA,SAAgB,IAAmB;CACjC,IAAM,EAAE,GAAG,MAAc,GAAS,EAC5B,KAAK,GAAa,MAA6B;EACnD,IAAM,IAAQ,EAAU,EAAI;AAC5B,SAAO,MAAU,IAAM,IAAW;IAE9B,EAAE,gBAAa,eAAY,iBAAc,GAAoB,EAE7D,EAAE,MAAM,GAAW,SAAS,MAChC,GAA2B,EACvB,EAAE,MAAM,GAAa,SAAS,MAClC,EAAwB,EACtB,WAAW,EAAE,OAAO,IAAI,EACzB,CAAC,EAEE,IAAQ,GAAW,gBACnB,IAAe,GAAa,aAAa,SAAS,EAAE;AAgB1D,QAdI,IAEA,kBAAC,GAAD;EACE,OAAO,EAAE,iCAAiC,gBAAgB;EAC1D,aAAa,EACX,uCACA,mCACD;YAED,kBAAC,GAAD;GAAiB,SAAQ;GAAO,OAAO;GAAK,CAAA;EACjC,CAAA,GAKf,kBAAC,GAAD;EACE,OAAO,EAAE,iCAAiC,gBAAgB;EAC1D,aAAa,EACX,uCACA,mCACD;EACD,SACE,kBAAC,UAAD;GACE,eAAgB,IAAc,GAAW,GAAG,GAAY;GACxD,WAAW,2EAA2E,IAAc,2CAA2C;aAE9I,IACC,kBAAA,GAAA,EAAA,UAAA;IACE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;IAC7B,EAAE,wCAAwC,OAAO;IAAC;IAClD,EAAoB,EAAY,QAAQ;IAAC;IACzC,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAC3B,EAAE,8CAA8C,cAAc,CAC9D,EAAA,CAAA;GAEE,CAAA;YAvBb,CA0BE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KACE,OAAO,EAAE,uCAAuC,QAAQ;KACxD,OAAO,EAAY,GAAO,cAAc,EAAE;KAC1C,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;KACnC,OAAO;MACL,OAAO;MACP,OAAO,EACL,6CACA,eACD;MACF;KACD,CAAA;IACF,kBAAC,GAAD;KACE,OAAO,EAAE,0CAA0C,WAAW;KAC9D,OAAO,EAAY,GAAO,iBAAiB,EAAE;KAC7C,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;KACnC,aAAa,GAAG,GAAO,gBAAgB,EAAE,GAAG,EAAE,yCAAyC,UAAU;KACjG,CAAA;IACF,kBAAC,GAAD;KACE,OAAO,EACL,8CACA,gBACD;KACD,OAAO,GAAO,gBAAgB;KAC9B,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;KACnC,aAAa,EACX,kDACA,oBACD;KACD,CAAA;IACF,kBAAC,GAAD;KACE,OAAO,EAAE,6CAA6C,eAAe;KACrE,OACE,IACI,EAAoB,EAAY,QAAQ,GACxC,EAAE,yCAAyC,UAAU;KAE3D,MAEI,EADF,IACG,IAEA,GAFD,EAAO,WAAU,WAAY,CAED;KAGhC,CAAA;IACE;MAEN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAE,yCAAyC,kBAAkB;IAC3D,CAAA,EACJ,IACC,kBAAC,GAAD;IAAiB,SAAQ;IAAO,OAAO;IAAK,CAAA,GAC1C,EAAa,WAAW,IAC1B,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,yCACA,wDACD;KACC,CAAA;IACA,CAAA,GAEN,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAa,KAAK,MACjB,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAM,SAAS,QACd,EACE,6CACA,aACD;OACD,CAAA,EACJ,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAM,eACL,EACE,iDACA,iBACD;OACD,CAAA,CACA;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBACV,GAAa,EAAM,YAAY,KAAK,KAAK;OACxC,CAAA,EACJ,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAM,YACH,EACE,kDACA,UACD,GACD,EACE,oDACA,YACD;OACA,CAAA,CACH;QACF;OAnCC,EAAM,GAmCP,CACN;IACE,CAAA,CAEJ;KACK"}
1
+ {"version":3,"file":"TimeTrackingPage.js","names":[],"sources":["../../../../src/timecampus/pages/time/TimeTrackingPage.tsx"],"sourcesContent":["import { Clock, Play, Pause, Square } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { formatHours, formatTimerDuration } from \"../../utils/helpers\";\nimport { useTimeCampusStore } from \"../../store/timecampusStore\";\nimport {\n useGetTimeEntryStatsQuery,\n useListTimeEntriesQuery,\n type TimeEntryFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\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, startTimer, stopTimer } = useTimeCampusStore();\n\n const { data: statsData, loading: statsLoading } =\n useGetTimeEntryStatsQuery();\n const { data: entriesData, loading: entriesLoading } =\n useListTimeEntriesQuery({\n variables: { limit: 10 },\n });\n\n const stats = statsData?.timeEntryStats;\n const todayEntries = entriesData?.timeEntries?.items ?? [];\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={() => (activeTimer ? stopTimer() : startTimer())}\n className={`inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium ${activeTimer ? \"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 {activeTimer ? (\n <>\n <Square className=\"h-4 w-4\" />\n {t(\"timecampus.timeTracking.actions.stop\", \"Stop\")} (\n {formatTimerDuration(activeTimer.seconds)})\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 <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(stats?.totalHours ?? 0)}\n icon={<Clock className=\"h-5 w-5\" />}\n trend={{\n value: 15,\n label: t(\n \"timecampus.timeTracking.stats.vsYesterday\",\n \"vs yesterday\",\n ),\n }}\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=\"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 {t(\"timecampus.timeTracking.entries.title\", \"Today's Entries\")}\n </h3>\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 />\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 justify-between rounded-lg border border-border-subtle p-3 transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n {entry.project?.name ??\n t(\n \"timecampus.timeTracking.entries.noProject\",\n \"No Project\",\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {entry.description ??\n t(\n \"timecampus.timeTracking.entries.noDescription\",\n \"No description\",\n )}\n </p>\n </div>\n <div className=\"ml-4 text-right\">\n <p className=\"text-sm font-mono font-medium text-text-primary\">\n {formatHours((entry.duration ?? 0) / 3600)}\n </p>\n <span className=\"inline-flex items-center rounded-full bg-status-success-bg px-2 py-0.5 text-xs font-medium text-status-success-text\">\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 </span>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,80 +1,114 @@
1
- import { formatTimerDuration as e } from "../../utils/helpers.js";
2
- import { PageLayout as t } from "../../components/PageLayout.js";
1
+ import { useTimeCampusNavigate as e } from "../../providers/TimeCampusProvider.js";
2
+ import { formatTimerDuration as t } from "../../utils/helpers.js";
3
3
  import { LoadingSkeleton as n } from "../../components/LoadingSkeleton.js";
4
4
  import { useTimeCampusStore as r } from "../../store/timecampusStore.js";
5
5
  import { useListRunningTimersQuery as i } from "../../../generated/wspace-operations.js";
6
- import { Play as a, RotateCcw as o, Square as s } from "lucide-react";
7
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
+ import { PageLayout as a } from "../../components/PageLayout.js";
7
+ import { EmptyState as o } from "../../components/EmptyState.js";
8
+ import { Play as s, RotateCcw as c, Square as l } from "lucide-react";
9
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
10
+ import { GlassCard as f, NextSteps as p } from "@burdenoff/fe-libs/ui";
8
11
  //#region src/timecampus/pages/time/TimerPage.tsx
9
- function u() {
10
- let { activeTimer: u, startTimer: d, stopTimer: f } = r(), { data: p, loading: m } = i(), h = p?.runningTimers ?? [];
11
- return /* @__PURE__ */ c(t, {
12
+ function m() {
13
+ let { activeTimer: m, startTimer: h, stopTimer: g } = r(), _ = e(), { data: v, loading: y } = i(), b = v?.runningTimers ?? [];
14
+ return /* @__PURE__ */ u(a, {
12
15
  title: "Timer",
13
16
  description: "Focus timer for deep work sessions",
14
- children: /* @__PURE__ */ l("div", {
17
+ 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.",
18
+ children: /* @__PURE__ */ d("div", {
15
19
  className: "mx-auto max-w-lg",
16
- children: [/* @__PURE__ */ l("div", {
17
- className: "rounded-xl border border-border-subtle bg-bg-surface p-8 text-center",
18
- children: [
19
- /* @__PURE__ */ c("p", {
20
- className: "text-6xl font-mono font-bold text-text-primary",
21
- children: u ? e(u.seconds) : "00:00:00"
22
- }),
23
- u?.description && /* @__PURE__ */ c("p", {
24
- className: "mt-2 text-sm text-text-secondary",
25
- children: u.description
26
- }),
27
- /* @__PURE__ */ l("div", {
28
- className: "mt-6 flex items-center justify-center gap-3",
29
- children: [u ? /* @__PURE__ */ l("button", {
30
- onClick: f,
31
- className: "inline-flex items-center gap-2 rounded-md bg-red-500 px-6 py-3 text-sm font-medium text-white hover:bg-red-600",
32
- children: [/* @__PURE__ */ c(s, { className: "h-4 w-4" }), "Stop"]
33
- }) : /* @__PURE__ */ l("button", {
34
- onClick: () => d(),
35
- 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",
36
- children: [/* @__PURE__ */ c(a, { className: "h-4 w-4" }), "Start"]
37
- }), /* @__PURE__ */ l("button", {
38
- className: "inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-3 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
39
- children: [/* @__PURE__ */ c(o, { className: "h-4 w-4" }), "Reset"]
40
- })]
41
- })
42
- ]
43
- }), /* @__PURE__ */ l("div", {
44
- className: "mt-6 rounded-xl border border-border-subtle bg-bg-surface p-6",
45
- children: [/* @__PURE__ */ c("h3", {
46
- className: "mb-4 text-sm font-semibold text-text-primary",
47
- children: "Running Timers"
48
- }), m ? /* @__PURE__ */ c(n, {
49
- variant: "list",
50
- count: 2
51
- }) : h.length === 0 ? /* @__PURE__ */ c("p", {
52
- className: "text-center text-sm text-text-secondary py-4",
53
- children: "No active timers on the server"
54
- }) : /* @__PURE__ */ c("div", {
55
- className: "flex flex-col gap-2",
56
- children: h.map((t) => /* @__PURE__ */ l("div", {
57
- className: "flex items-center justify-between rounded-lg border border-border-subtle p-3",
58
- children: [/* @__PURE__ */ l("div", {
59
- className: "min-w-0 flex-1",
60
- children: [/* @__PURE__ */ c("p", {
61
- className: "text-sm font-medium text-text-primary",
62
- children: t.timeEntry.project?.name ?? "No Project"
63
- }), /* @__PURE__ */ c("p", {
64
- className: "text-xs text-text-secondary",
65
- children: t.timeEntry.description ?? "No description"
20
+ children: [
21
+ /* @__PURE__ */ u(p, {
22
+ storageKey: "timecampus-timer",
23
+ className: "mb-6",
24
+ steps: [
25
+ {
26
+ id: "start",
27
+ label: m ? "Stop the running timer" : "Start the timer",
28
+ description: m ? "End this session and save it as a time entry." : "Begin tracking the task you're working on right now.",
29
+ onClick: () => m ? g() : h()
30
+ },
31
+ {
32
+ id: "assign-project",
33
+ label: "Assign work to a project",
34
+ description: "Pick a project so this time bills correctly.",
35
+ onClick: () => _("/projects")
36
+ },
37
+ {
38
+ id: "review-entries",
39
+ label: "Review your time entries",
40
+ description: "Check and tidy everything you've logged.",
41
+ onClick: () => _("/time/entries")
42
+ }
43
+ ]
44
+ }),
45
+ /* @__PURE__ */ d(f, {
46
+ treatment: "glass",
47
+ glow: !0,
48
+ className: "p-8 text-center",
49
+ children: [
50
+ /* @__PURE__ */ u("p", {
51
+ className: "text-6xl font-mono font-bold text-text-primary",
52
+ children: m ? t(m.seconds) : "00:00:00"
53
+ }),
54
+ m?.description && /* @__PURE__ */ u("p", {
55
+ className: "mt-2 text-sm text-text-secondary",
56
+ children: m.description
57
+ }),
58
+ /* @__PURE__ */ d("div", {
59
+ className: "mt-6 flex items-center justify-center gap-3",
60
+ children: [m ? /* @__PURE__ */ d("button", {
61
+ onClick: g,
62
+ 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",
63
+ children: [/* @__PURE__ */ u(l, { className: "h-4 w-4" }), "Stop"]
64
+ }) : /* @__PURE__ */ d("button", {
65
+ onClick: () => h(),
66
+ 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",
67
+ children: [/* @__PURE__ */ u(s, { className: "h-4 w-4" }), "Start"]
68
+ }), /* @__PURE__ */ d("button", {
69
+ className: "inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-3 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
70
+ children: [/* @__PURE__ */ u(c, { className: "h-4 w-4" }), "Reset"]
66
71
  })]
67
- }), /* @__PURE__ */ c("p", {
68
- className: "ml-4 text-sm font-mono font-medium text-text-primary",
69
- children: e(t.elapsedSeconds)
70
- })]
71
- }, t.timeEntry.id))
72
- })]
73
- })]
72
+ })
73
+ ]
74
+ }),
75
+ /* @__PURE__ */ d("div", {
76
+ className: "mt-6 rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6",
77
+ children: [/* @__PURE__ */ u("h3", {
78
+ className: "mb-4 text-sm font-semibold text-text-primary",
79
+ children: "Running Timers"
80
+ }), y ? /* @__PURE__ */ u(n, {
81
+ variant: "list",
82
+ count: 2
83
+ }) : b.length === 0 ? /* @__PURE__ */ u(o, {
84
+ illustration: "empty-sessions",
85
+ title: "No active timers on the server",
86
+ description: "Timers you start sync here so you can pick them up on any device."
87
+ }) : /* @__PURE__ */ u("div", {
88
+ className: "flex flex-col gap-2",
89
+ children: b.map((e) => /* @__PURE__ */ d("div", {
90
+ className: "flex items-center justify-between rounded-lg border border-border-subtle p-3",
91
+ children: [/* @__PURE__ */ d("div", {
92
+ className: "min-w-0 flex-1",
93
+ children: [/* @__PURE__ */ u("p", {
94
+ className: "text-sm font-medium text-text-primary",
95
+ children: e.timeEntry.project?.name ?? "No Project"
96
+ }), /* @__PURE__ */ u("p", {
97
+ className: "text-xs text-text-secondary",
98
+ children: e.timeEntry.description ?? "No description"
99
+ })]
100
+ }), /* @__PURE__ */ u("p", {
101
+ className: "ml-4 text-sm font-mono font-medium text-text-primary",
102
+ children: t(e.elapsedSeconds)
103
+ })]
104
+ }, e.timeEntry.id))
105
+ })]
106
+ })
107
+ ]
74
108
  })
75
109
  });
76
110
  }
77
111
  //#endregion
78
- export { u as TimerPage };
112
+ export { m as TimerPage };
79
113
 
80
114
  //# sourceMappingURL=TimerPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TimerPage.js","names":[],"sources":["../../../../src/timecampus/pages/time/TimerPage.tsx"],"sourcesContent":["import { Play, Square, RotateCcw } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { formatTimerDuration } from \"../../utils/helpers\";\nimport { useTimeCampusStore } from \"../../store/timecampusStore\";\nimport {\n useListRunningTimersQuery,\n type ListRunningTimersQuery,\n} from \"@/generated/wspace-operations\";\n\ntype RunningTimerItem = ListRunningTimersQuery[\"runningTimers\"][number];\n\nexport function TimerPage() {\n const { activeTimer, startTimer, stopTimer } = useTimeCampusStore();\n\n const { data: runningData, loading: runningLoading } =\n useListRunningTimersQuery();\n\n const runningTimers = runningData?.runningTimers ?? [];\n\n return (\n <PageLayout title=\"Timer\" description=\"Focus timer for deep work sessions\">\n <div className=\"mx-auto max-w-lg\">\n <div className=\"rounded-xl border border-border-subtle bg-bg-surface p-8 text-center\">\n <p className=\"text-6xl font-mono font-bold text-text-primary\">\n {activeTimer\n ? formatTimerDuration(activeTimer.seconds)\n : \"00:00:00\"}\n </p>\n {activeTimer?.description && (\n <p className=\"mt-2 text-sm text-text-secondary\">\n {activeTimer.description}\n </p>\n )}\n <div className=\"mt-6 flex items-center justify-center gap-3\">\n {activeTimer ? (\n <button\n onClick={stopTimer}\n className=\"inline-flex items-center gap-2 rounded-md bg-red-500 px-6 py-3 text-sm font-medium text-white hover:bg-red-600\"\n >\n <Square className=\"h-4 w-4\" />\n Stop\n </button>\n ) : (\n <button\n onClick={() => startTimer()}\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\"\n >\n <Play className=\"h-4 w-4\" />\n Start\n </button>\n )}\n <button className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-3 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\">\n <RotateCcw className=\"h-4 w-4\" />\n Reset\n </button>\n </div>\n </div>\n\n {/* Server-side running timers */}\n <div className=\"mt-6 rounded-xl border border-border-subtle bg-bg-surface p-6\">\n <h3 className=\"mb-4 text-sm font-semibold text-text-primary\">\n Running Timers\n </h3>\n {runningLoading ? (\n <LoadingSkeleton variant=\"list\" count={2} />\n ) : runningTimers.length === 0 ? (\n <p className=\"text-center text-sm text-text-secondary py-4\">\n No active timers on the server\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {runningTimers.map((timer: RunningTimerItem) => (\n <div\n key={timer.timeEntry.id}\n className=\"flex items-center justify-between rounded-lg border border-border-subtle p-3\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n {timer.timeEntry.project?.name ?? \"No Project\"}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {timer.timeEntry.description ?? \"No description\"}\n </p>\n </div>\n <p className=\"ml-4 text-sm font-mono font-medium text-text-primary\">\n {formatTimerDuration(timer.elapsedSeconds)}\n </p>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;AAYA,SAAgB,IAAY;CAC1B,IAAM,EAAE,gBAAa,eAAY,iBAAc,GAAoB,EAE7D,EAAE,MAAM,GAAa,SAAS,MAClC,GAA2B,EAEvB,IAAgB,GAAa,iBAAiB,EAAE;AAEtD,QACE,kBAAC,GAAD;EAAY,OAAM;EAAQ,aAAY;YACpC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,KAAD;MAAG,WAAU;gBACV,IACG,EAAoB,EAAY,QAAQ,GACxC;MACF,CAAA;KACH,GAAa,eACZ,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAY;MACX,CAAA;KAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,IACC,kBAAC,UAAD;OACE,SAAS;OACT,WAAU;iBAFZ,CAIE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAAA,OAEvB;WAET,kBAAC,UAAD;OACE,eAAe,GAAY;OAC3B,WAAU;iBAFZ,CAIE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,QAErB;UAEX,kBAAC,UAAD;OAAQ,WAAU;iBAAlB,CACE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,QAE1B;SACL;;KACF;OAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAA+C;KAExD,CAAA,EACJ,IACC,kBAAC,GAAD;KAAiB,SAAQ;KAAO,OAAO;KAAK,CAAA,GAC1C,EAAc,WAAW,IAC3B,kBAAC,KAAD;KAAG,WAAU;eAA+C;KAExD,CAAA,GAEJ,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAc,KAAK,MAClB,kBAAC,OAAD;MAEE,WAAU;gBAFZ,CAIE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAM,UAAU,SAAS,QAAQ;QAChC,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAM,UAAU,eAAe;QAC9B,CAAA,CACA;UACN,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAoB,EAAM,eAAe;OACxC,CAAA,CACA;QAdC,EAAM,UAAU,GAcjB,CACN;KACE,CAAA,CAEJ;MACF;;EACK,CAAA"}
1
+ {"version":3,"file":"TimerPage.js","names":[],"sources":["../../../../src/timecampus/pages/time/TimerPage.tsx"],"sourcesContent":["import { Play, Square, RotateCcw } from \"lucide-react\";\nimport { GlassCard, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { formatTimerDuration } from \"../../utils/helpers\";\nimport { useTimeCampusStore } from \"../../store/timecampusStore\";\nimport {\n useListRunningTimersQuery,\n type ListRunningTimersQuery,\n} from \"@/generated/wspace-operations\";\n\ntype RunningTimerItem = ListRunningTimersQuery[\"runningTimers\"][number];\n\nexport function TimerPage() {\n const { activeTimer, startTimer, stopTimer } = useTimeCampusStore();\n const navigate = useTimeCampusNavigate();\n\n const { data: runningData, loading: runningLoading } =\n useListRunningTimersQuery();\n\n const runningTimers = runningData?.runningTimers ?? [];\n\n return (\n <PageLayout\n title=\"Timer\"\n description=\"Focus timer for deep work sessions\"\n 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.\"\n >\n <div className=\"mx-auto max-w-lg\">\n <NextSteps\n storageKey=\"timecampus-timer\"\n className=\"mb-6\"\n steps={[\n {\n id: \"start\",\n label: activeTimer ? \"Stop the running timer\" : \"Start the timer\",\n description: activeTimer\n ? \"End this session and save it as a time entry.\"\n : \"Begin tracking the task you're working on right now.\",\n onClick: () => (activeTimer ? stopTimer() : startTimer()),\n },\n {\n id: \"assign-project\",\n label: \"Assign work to a project\",\n description: \"Pick a project so this time bills correctly.\",\n onClick: () => navigate(\"/projects\"),\n },\n {\n id: \"review-entries\",\n label: \"Review your time entries\",\n description: \"Check and tidy everything you've logged.\",\n onClick: () => navigate(\"/time/entries\"),\n },\n ]}\n />\n <GlassCard treatment=\"glass\" glow className=\"p-8 text-center\">\n <p className=\"text-6xl font-mono font-bold text-text-primary\">\n {activeTimer\n ? formatTimerDuration(activeTimer.seconds)\n : \"00:00:00\"}\n </p>\n {activeTimer?.description && (\n <p className=\"mt-2 text-sm text-text-secondary\">\n {activeTimer.description}\n </p>\n )}\n <div className=\"mt-6 flex items-center justify-center gap-3\">\n {activeTimer ? (\n <button\n onClick={stopTimer}\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\"\n >\n <Square className=\"h-4 w-4\" />\n Stop\n </button>\n ) : (\n <button\n onClick={() => startTimer()}\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\"\n >\n <Play className=\"h-4 w-4\" />\n Start\n </button>\n )}\n <button className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-3 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\">\n <RotateCcw className=\"h-4 w-4\" />\n Reset\n </button>\n </div>\n </GlassCard>\n\n {/* Server-side running timers */}\n <div className=\"mt-6 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 Running Timers\n </h3>\n {runningLoading ? (\n <LoadingSkeleton variant=\"list\" count={2} />\n ) : runningTimers.length === 0 ? (\n <EmptyState\n illustration=\"empty-sessions\"\n title=\"No active timers on the server\"\n description=\"Timers you start sync here so you can pick them up on any device.\"\n />\n ) : (\n <div className=\"flex flex-col gap-2\">\n {runningTimers.map((timer: RunningTimerItem) => (\n <div\n key={timer.timeEntry.id}\n className=\"flex items-center justify-between rounded-lg border border-border-subtle p-3\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n {timer.timeEntry.project?.name ?? \"No Project\"}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {timer.timeEntry.description ?? \"No description\"}\n </p>\n </div>\n <p className=\"ml-4 text-sm font-mono font-medium text-text-primary\">\n {formatTimerDuration(timer.elapsedSeconds)}\n </p>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;AAeA,SAAgB,IAAY;CAC1B,IAAM,EAAE,gBAAa,eAAY,iBAAc,GAAoB,EAC7D,IAAW,GAAuB,EAElC,EAAE,MAAM,GAAa,SAAS,MAClC,GAA2B,EAEvB,IAAgB,GAAa,iBAAiB,EAAE;AAEtD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;YAER,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KACE,YAAW;KACX,WAAU;KACV,OAAO;MACL;OACE,IAAI;OACJ,OAAO,IAAc,2BAA2B;OAChD,aAAa,IACT,kDACA;OACJ,eAAgB,IAAc,GAAW,GAAG,GAAY;OACzD;MACD;OACE,IAAI;OACJ,OAAO;OACP,aAAa;OACb,eAAe,EAAS,YAAY;OACrC;MACD;OACE,IAAI;OACJ,OAAO;OACP,aAAa;OACb,eAAe,EAAS,gBAAgB;OACzC;MACF;KACD,CAAA;IACF,kBAAC,GAAD;KAAW,WAAU;KAAQ,MAAA;KAAK,WAAU;eAA5C;MACE,kBAAC,KAAD;OAAG,WAAU;iBACV,IACG,EAAoB,EAAY,QAAQ,GACxC;OACF,CAAA;MACH,GAAa,eACZ,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAY;OACX,CAAA;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACG,IACC,kBAAC,UAAD;QACE,SAAS;QACT,WAAU;kBAFZ,CAIE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAAA,OAEvB;YAET,kBAAC,UAAD;QACE,eAAe,GAAY;QAC3B,WAAU;kBAFZ,CAIE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,QAErB;WAEX,kBAAC,UAAD;QAAQ,WAAU;kBAAlB,CACE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,QAE1B;UACL;;MACI;;IAGZ,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA+C;MAExD,CAAA,EACJ,IACC,kBAAC,GAAD;MAAiB,SAAQ;MAAO,OAAO;MAAK,CAAA,GAC1C,EAAc,WAAW,IAC3B,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,CAAA,GAEF,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAc,KAAK,MAClB,kBAAC,OAAD;OAEE,WAAU;iBAFZ,CAIE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAM,UAAU,SAAS,QAAQ;SAChC,CAAA,EACJ,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAM,UAAU,eAAe;SAC9B,CAAA,CACA;WACN,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAoB,EAAM,eAAe;QACxC,CAAA,CACA;SAdC,EAAM,UAAU,GAcjB,CACN;MACE,CAAA,CAEJ;;IACF;;EACK,CAAA"}
@@ -1,83 +1,87 @@
1
1
  import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
2
2
  import { formatDate as t, formatHours as n, getInitials as r, getStatusColor as i } from "../../utils/helpers.js";
3
- import { PageLayout as a } from "../../components/PageLayout.js";
4
- import { StatCard as o } from "../../components/StatCard.js";
5
- import { LoadingSkeleton as s } from "../../components/LoadingSkeleton.js";
6
- import { useApproveRequestMutation as c, useGetApprovalStatsQuery as l, useListApprovalRequestsQuery as u, useRejectRequestMutation as d } from "../../../generated/wspace-operations.js";
7
- import { CheckCircle as f, Clock as p, XCircle as m } from "lucide-react";
8
- import { jsx as h, jsxs as g } from "react/jsx-runtime";
3
+ import { StatCard as a } from "../../components/StatCard.js";
4
+ import { LoadingSkeleton as o } from "../../components/LoadingSkeleton.js";
5
+ import { useApproveRequestMutation as s, useGetApprovalStatsQuery as c, useListApprovalRequestsQuery as l, useRejectRequestMutation as u } from "../../../generated/wspace-operations.js";
6
+ import { PageLayout as d } from "../../components/PageLayout.js";
7
+ import { EmptyState as f } from "../../components/EmptyState.js";
8
+ import { CheckCircle as p, Clock as m, XCircle as h } from "lucide-react";
9
+ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
9
10
  //#region src/timecampus/pages/time-approval/TimeApprovalPage.tsx
10
- function _() {
11
- let { workspaceId: _ = "" } = e(), { data: v, loading: y, error: b } = u(), { data: x, loading: S, error: C } = l(), [w, { loading: T }] = c(), [E, { loading: D }] = d(), O = v?.approvalRequests?.items ?? [], k = x?.approvalStats, A = T || D, j = async (e) => {
12
- await w({ variables: { id: e } });
13
- }, M = async (e) => {
14
- await E({ variables: {
11
+ function v() {
12
+ let { workspaceId: v = "" } = e(), { data: y, loading: b, error: x } = l(), { data: S, loading: C, error: w } = c(), [T, { loading: E }] = s(), [D, { loading: O }] = u(), k = y?.approvalRequests?.items ?? [], A = S?.approvalStats, j = E || O, M = async (e) => {
13
+ await T({ variables: { id: e } });
14
+ }, N = async (e) => {
15
+ await D({ variables: {
15
16
  id: e,
16
17
  reason: "Rejected by reviewer"
17
18
  } });
18
19
  };
19
- return y && S ? /* @__PURE__ */ h(a, {
20
+ return b && C ? /* @__PURE__ */ g(d, {
20
21
  title: "Time Approval",
21
22
  description: "Review and approve team timesheets",
22
- children: /* @__PURE__ */ h(s, {
23
+ children: /* @__PURE__ */ g(o, {
23
24
  variant: "list",
24
25
  count: 4
25
26
  })
26
- }) : b && C ? /* @__PURE__ */ h(a, {
27
+ }) : x && w ? /* @__PURE__ */ g(d, {
27
28
  title: "Time Approval",
28
29
  description: "Review and approve team timesheets",
29
- children: /* @__PURE__ */ h("div", {
30
- className: "rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-600 dark:border-red-800 dark:bg-red-900/20 dark:text-red-400",
30
+ children: /* @__PURE__ */ g("div", {
31
+ className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
31
32
  children: "Failed to load approval data. Please try again later."
32
33
  })
33
- }) : /* @__PURE__ */ g(a, {
34
+ }) : /* @__PURE__ */ _(d, {
34
35
  title: "Time Approval",
35
36
  description: "Review and approve team timesheets",
36
- children: [/* @__PURE__ */ g("div", {
37
+ purpose: "The reviewer's queue for signing off team timesheets before they're billed or paid — approve or reject submissions in one place so payroll and invoices go out accurate and on time.",
38
+ children: [/* @__PURE__ */ _("div", {
37
39
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
38
40
  children: [
39
- /* @__PURE__ */ h(o, {
41
+ /* @__PURE__ */ g(a, {
42
+ featured: !0,
40
43
  title: "Pending Approvals",
41
- value: String(k?.pendingCount ?? 0),
42
- icon: /* @__PURE__ */ h(p, { className: "h-5 w-5" }),
43
- iconBgColor: "bg-amber-100 text-amber-600 dark:bg-amber-900/30 dark:text-amber-400"
44
+ value: String(A?.pendingCount ?? 0),
45
+ icon: /* @__PURE__ */ g(m, { className: "h-5 w-5" }),
46
+ iconBgColor: "bg-status-warning-bg text-status-warning-text"
44
47
  }),
45
- /* @__PURE__ */ h(o, {
48
+ /* @__PURE__ */ g(a, {
46
49
  title: "Approved This Week",
47
- value: String(k?.approvedCount ?? 0),
48
- icon: /* @__PURE__ */ h(f, { className: "h-5 w-5" }),
49
- iconBgColor: "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400"
50
+ value: String(A?.approvedCount ?? 0),
51
+ icon: /* @__PURE__ */ g(p, { className: "h-5 w-5" }),
52
+ iconBgColor: "bg-status-success-bg text-status-success-text"
50
53
  }),
51
- /* @__PURE__ */ h(o, {
54
+ /* @__PURE__ */ g(a, {
52
55
  title: "Rejected",
53
- value: String(k?.rejectedCount ?? 0),
54
- icon: /* @__PURE__ */ h(m, { className: "h-5 w-5" }),
55
- iconBgColor: "bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400"
56
+ value: String(A?.rejectedCount ?? 0),
57
+ icon: /* @__PURE__ */ g(h, { className: "h-5 w-5" }),
58
+ iconBgColor: "bg-status-error-bg text-status-error-text"
56
59
  }),
57
- /* @__PURE__ */ h(o, {
60
+ /* @__PURE__ */ g(a, {
58
61
  title: "Avg. Review Time",
59
- value: k?.averageReviewTime ? `${Math.round(k.averageReviewTime)}h` : "N/A",
62
+ value: A?.averageReviewTime ? `${Math.round(A.averageReviewTime)}h` : "N/A",
60
63
  description: "Target: 24h"
61
64
  })
62
65
  ]
63
- }), /* @__PURE__ */ h("div", {
64
- className: "rounded-xl border border-border-subtle bg-bg-surface",
66
+ }), k.length === 0 && !b ? /* @__PURE__ */ g(f, {
67
+ illustration: "empty-notifications",
68
+ title: "No approval requests found",
69
+ description: "You're all caught up — submitted timesheets waiting on your review will appear here."
70
+ }) : /* @__PURE__ */ g("div", {
71
+ className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop",
65
72
  children: /* @__PURE__ */ g("div", {
66
73
  className: "divide-y divide-border",
67
- children: [O.length === 0 && !y && /* @__PURE__ */ h("div", {
68
- className: "p-4 text-center text-sm text-text-secondary",
69
- children: "No approval requests found."
70
- }), O.map((e) => /* @__PURE__ */ g("div", {
74
+ children: k.map((e) => /* @__PURE__ */ _("div", {
71
75
  className: "flex items-center justify-between p-4 hover:bg-bg-sunken/50 transition-colors",
72
- children: [/* @__PURE__ */ g("div", {
76
+ children: [/* @__PURE__ */ _("div", {
73
77
  className: "flex items-center gap-3",
74
- children: [/* @__PURE__ */ h("div", {
78
+ children: [/* @__PURE__ */ g("div", {
75
79
  className: "flex h-9 w-9 items-center justify-center rounded-full bg-action-primary-bg/10 text-sm font-medium text-primary",
76
80
  children: r(e.userId)
77
- }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("p", {
81
+ }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
78
82
  className: "text-sm font-medium text-text-primary",
79
83
  children: e.userId
80
- }), /* @__PURE__ */ g("p", {
84
+ }), /* @__PURE__ */ _("p", {
81
85
  className: "text-xs text-text-secondary",
82
86
  children: [
83
87
  e.period,
@@ -86,45 +90,45 @@ function _() {
86
90
  t(e.submittedAt)
87
91
  ]
88
92
  })] })]
89
- }), /* @__PURE__ */ g("div", {
93
+ }), /* @__PURE__ */ _("div", {
90
94
  className: "flex items-center gap-4",
91
95
  children: [
92
- /* @__PURE__ */ g("div", {
96
+ /* @__PURE__ */ _("div", {
93
97
  className: "text-right",
94
- children: [/* @__PURE__ */ h("p", {
98
+ children: [/* @__PURE__ */ g("p", {
95
99
  className: "text-sm font-medium text-text-primary",
96
100
  children: n(e.totalHours)
97
- }), /* @__PURE__ */ g("p", {
101
+ }), /* @__PURE__ */ _("p", {
98
102
  className: "text-xs text-text-secondary",
99
103
  children: [n(e.billableHours), " billable"]
100
104
  })]
101
105
  }),
102
- /* @__PURE__ */ h("span", {
106
+ /* @__PURE__ */ g("span", {
103
107
  className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${i(e.status)}`,
104
108
  children: e.status
105
109
  }),
106
- e.status === "PENDING" && /* @__PURE__ */ g("div", {
110
+ e.status === "PENDING" && /* @__PURE__ */ _("div", {
107
111
  className: "flex items-center gap-1",
108
- children: [/* @__PURE__ */ h("button", {
109
- onClick: () => j(e.id),
110
- disabled: A,
111
- className: "rounded-md p-1.5 text-emerald-600 hover:bg-emerald-50 dark:hover:bg-emerald-900/20 disabled:opacity-50",
112
- children: /* @__PURE__ */ h(f, { className: "h-4 w-4" })
113
- }), /* @__PURE__ */ h("button", {
112
+ children: [/* @__PURE__ */ g("button", {
114
113
  onClick: () => M(e.id),
115
- disabled: A,
116
- className: "rounded-md p-1.5 text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 disabled:opacity-50",
117
- children: /* @__PURE__ */ h(m, { className: "h-4 w-4" })
114
+ disabled: j,
115
+ className: "rounded-md p-1.5 text-status-success-text hover:bg-status-success-bg dark:hover:bg-status-success-text/20 disabled:opacity-50",
116
+ children: /* @__PURE__ */ g(p, { className: "h-4 w-4" })
117
+ }), /* @__PURE__ */ g("button", {
118
+ onClick: () => N(e.id),
119
+ disabled: j,
120
+ className: "rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg dark:hover:bg-status-error-text/20 disabled:opacity-50",
121
+ children: /* @__PURE__ */ g(h, { className: "h-4 w-4" })
118
122
  })]
119
123
  })
120
124
  ]
121
125
  })]
122
- }, e.id))]
126
+ }, e.id))
123
127
  })
124
128
  })]
125
129
  });
126
130
  }
127
131
  //#endregion
128
- export { _ as TimeApprovalPage };
132
+ export { v as TimeApprovalPage };
129
133
 
130
134
  //# sourceMappingURL=TimeApprovalPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TimeApprovalPage.js","names":[],"sources":["../../../../src/timecampus/pages/time-approval/TimeApprovalPage.tsx"],"sourcesContent":["import { CheckCircle, XCircle, Clock } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport {\n getStatusColor,\n getInitials,\n formatHours,\n formatDate,\n} from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListApprovalRequestsQuery,\n useGetApprovalStatsQuery,\n useApproveRequestMutation,\n useRejectRequestMutation,\n type ApprovalRequestFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nexport function TimeApprovalPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const {\n data: requestsData,\n loading: requestsLoading,\n error: requestsError,\n } = useListApprovalRequestsQuery();\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n } = useGetApprovalStatsQuery();\n const [approveRequest, { loading: approving }] = useApproveRequestMutation();\n const [rejectRequest, { loading: rejecting }] = useRejectRequestMutation();\n\n const requests = requestsData?.approvalRequests?.items ?? [];\n const stats = statsData?.approvalStats;\n const mutating = approving || rejecting;\n\n const handleApprove = async (id: string) => {\n await approveRequest({ variables: { id } });\n };\n\n const handleReject = async (id: string) => {\n await rejectRequest({ variables: { id, reason: \"Rejected by reviewer\" } });\n };\n\n if (requestsLoading && statsLoading) {\n return (\n <PageLayout\n title=\"Time Approval\"\n description=\"Review and approve team timesheets\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (requestsError && statsError) {\n return (\n <PageLayout\n title=\"Time Approval\"\n description=\"Review and approve team timesheets\"\n >\n <div className=\"rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-600 dark:border-red-800 dark:bg-red-900/20 dark:text-red-400\">\n Failed to load approval data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Time Approval\"\n description=\"Review and approve team timesheets\"\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n title=\"Pending Approvals\"\n value={String(stats?.pendingCount ?? 0)}\n icon={<Clock className=\"h-5 w-5\" />}\n iconBgColor=\"bg-amber-100 text-amber-600 dark:bg-amber-900/30 dark:text-amber-400\"\n />\n <StatCard\n title=\"Approved This Week\"\n value={String(stats?.approvedCount ?? 0)}\n icon={<CheckCircle className=\"h-5 w-5\" />}\n iconBgColor=\"bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400\"\n />\n <StatCard\n title=\"Rejected\"\n value={String(stats?.rejectedCount ?? 0)}\n icon={<XCircle className=\"h-5 w-5\" />}\n iconBgColor=\"bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400\"\n />\n <StatCard\n title=\"Avg. Review Time\"\n value={\n stats?.averageReviewTime\n ? `${Math.round(stats.averageReviewTime)}h`\n : \"N/A\"\n }\n description=\"Target: 24h\"\n />\n </div>\n\n <div className=\"rounded-xl border border-border-subtle bg-bg-surface\">\n <div className=\"divide-y divide-border\">\n {requests.length === 0 && !requestsLoading && (\n <div className=\"p-4 text-center text-sm text-text-secondary\">\n No approval requests found.\n </div>\n )}\n {requests.map((request: ApprovalRequestFieldsFragment) => (\n <div\n key={request.id}\n className=\"flex items-center justify-between p-4 hover:bg-bg-sunken/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 items-center justify-center rounded-full bg-action-primary-bg/10 text-sm font-medium text-primary\">\n {getInitials(request.userId)}\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {request.userId}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {request.period} &middot; Submitted{\" \"}\n {formatDate(request.submittedAt)}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-4\">\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-text-primary\">\n {formatHours(request.totalHours)}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {formatHours(request.billableHours)} billable\n </p>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(request.status)}`}\n >\n {request.status}\n </span>\n {request.status === \"PENDING\" && (\n <div className=\"flex items-center gap-1\">\n <button\n onClick={() => handleApprove(request.id)}\n disabled={mutating}\n className=\"rounded-md p-1.5 text-emerald-600 hover:bg-emerald-50 dark:hover:bg-emerald-900/20 disabled:opacity-50\"\n >\n <CheckCircle className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => handleReject(request.id)}\n disabled={mutating}\n className=\"rounded-md p-1.5 text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 disabled:opacity-50\"\n >\n <XCircle className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TimeApprovalPage.js","names":[],"sources":["../../../../src/timecampus/pages/time-approval/TimeApprovalPage.tsx"],"sourcesContent":["import { CheckCircle, XCircle, Clock } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport {\n getStatusColor,\n getInitials,\n formatHours,\n formatDate,\n} from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListApprovalRequestsQuery,\n useGetApprovalStatsQuery,\n useApproveRequestMutation,\n useRejectRequestMutation,\n type ApprovalRequestFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nexport function TimeApprovalPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const {\n data: requestsData,\n loading: requestsLoading,\n error: requestsError,\n } = useListApprovalRequestsQuery();\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n } = useGetApprovalStatsQuery();\n const [approveRequest, { loading: approving }] = useApproveRequestMutation();\n const [rejectRequest, { loading: rejecting }] = useRejectRequestMutation();\n\n const requests = requestsData?.approvalRequests?.items ?? [];\n const stats = statsData?.approvalStats;\n const mutating = approving || rejecting;\n\n const handleApprove = async (id: string) => {\n await approveRequest({ variables: { id } });\n };\n\n const handleReject = async (id: string) => {\n await rejectRequest({ variables: { id, reason: \"Rejected by reviewer\" } });\n };\n\n if (requestsLoading && statsLoading) {\n return (\n <PageLayout\n title=\"Time Approval\"\n description=\"Review and approve team timesheets\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (requestsError && statsError) {\n return (\n <PageLayout\n title=\"Time Approval\"\n description=\"Review and approve team timesheets\"\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 approval data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Time Approval\"\n description=\"Review and approve team timesheets\"\n purpose=\"The reviewer's queue for signing off team timesheets before they're billed or paid — approve or reject submissions in one place so payroll and invoices go out accurate and on time.\"\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Pending Approvals\"\n value={String(stats?.pendingCount ?? 0)}\n icon={<Clock className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-warning-bg text-status-warning-text\"\n />\n <StatCard\n title=\"Approved This Week\"\n value={String(stats?.approvedCount ?? 0)}\n icon={<CheckCircle className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n <StatCard\n title=\"Rejected\"\n value={String(stats?.rejectedCount ?? 0)}\n icon={<XCircle className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-error-bg text-status-error-text\"\n />\n <StatCard\n title=\"Avg. Review Time\"\n value={\n stats?.averageReviewTime\n ? `${Math.round(stats.averageReviewTime)}h`\n : \"N/A\"\n }\n description=\"Target: 24h\"\n />\n </div>\n\n {requests.length === 0 && !requestsLoading ? (\n <EmptyState\n illustration=\"empty-notifications\"\n title=\"No approval requests found\"\n description=\"You're all caught up — submitted timesheets waiting on your review will appear here.\"\n />\n ) : (\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop\">\n <div className=\"divide-y divide-border\">\n {requests.map((request: ApprovalRequestFieldsFragment) => (\n <div\n key={request.id}\n className=\"flex items-center justify-between p-4 hover:bg-bg-sunken/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 items-center justify-center rounded-full bg-action-primary-bg/10 text-sm font-medium text-primary\">\n {getInitials(request.userId)}\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {request.userId}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {request.period} &middot; Submitted{\" \"}\n {formatDate(request.submittedAt)}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-4\">\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-text-primary\">\n {formatHours(request.totalHours)}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {formatHours(request.billableHours)} billable\n </p>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(request.status)}`}\n >\n {request.status}\n </span>\n {request.status === \"PENDING\" && (\n <div className=\"flex items-center gap-1\">\n <button\n onClick={() => handleApprove(request.id)}\n disabled={mutating}\n className=\"rounded-md p-1.5 text-status-success-text hover:bg-status-success-bg dark:hover:bg-status-success-text/20 disabled:opacity-50\"\n >\n <CheckCircle className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => handleReject(request.id)}\n disabled={mutating}\n className=\"rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg dark:hover:bg-status-error-text/20 disabled:opacity-50\"\n >\n <XCircle className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}