@burdenoff/microfe-timecampus 2026.626.2 → 2026.703.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/generated/wspace-operations.js +996 -816
  2. package/dist/generated/wspace-operations.js.map +1 -1
  3. package/dist/generated/wspace-types.js +28 -0
  4. package/dist/generated/wspace-types.js.map +1 -0
  5. package/dist/index.js +4 -4
  6. package/dist/timecampus/TimeCampusRoutes.js +16 -12
  7. package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
  8. package/dist/timecampus/components/Modal.js +65 -0
  9. package/dist/timecampus/components/Modal.js.map +1 -0
  10. package/dist/timecampus/components/StatusBadge.js +28 -0
  11. package/dist/timecampus/components/StatusBadge.js.map +1 -0
  12. package/dist/timecampus/components/Toast.js +87 -0
  13. package/dist/timecampus/components/Toast.js.map +1 -0
  14. package/dist/timecampus/index.js +1 -1
  15. package/dist/timecampus/pages/HomePage.js +143 -135
  16. package/dist/timecampus/pages/HomePage.js.map +1 -1
  17. package/dist/timecampus/pages/applets/AppletStorePage.js +14 -19
  18. package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
  19. package/dist/timecampus/pages/applets/InstalledAppletsPage.js +49 -37
  20. package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
  21. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +315 -87
  22. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
  23. package/dist/timecampus/pages/channels/ChannelsPage.js +340 -46
  24. package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
  25. package/dist/timecampus/pages/client-portal/ClientPortalPage.js +374 -92
  26. package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
  27. package/dist/timecampus/pages/dashboard/ReportsPage.js +70 -37
  28. package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
  29. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +366 -60
  30. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
  31. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +461 -87
  32. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
  33. package/dist/timecampus/pages/geofencing/GeofencingPage.js +314 -55
  34. package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
  35. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +30 -27
  36. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
  37. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +269 -74
  38. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
  39. package/dist/timecampus/pages/meetings/ActionItemFormModal.js +127 -0
  40. package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -0
  41. package/dist/timecampus/pages/meetings/MeetingCreatePage.js +62 -52
  42. package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
  43. package/dist/timecampus/pages/meetings/MeetingDetailPage.js +361 -74
  44. package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
  45. package/dist/timecampus/pages/meetings/MeetingFormModal.js +156 -0
  46. package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -0
  47. package/dist/timecampus/pages/meetings/MeetingsPage.js +175 -74
  48. package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
  49. package/dist/timecampus/pages/meetings/ParticipantFormModal.js +105 -0
  50. package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -0
  51. package/dist/timecampus/pages/pomodoro/PomodoroPage.js +307 -67
  52. package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
  53. package/dist/timecampus/pages/projects/ProjectCreatePage.js +51 -48
  54. package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
  55. package/dist/timecampus/pages/projects/ProjectDetailPage.js +248 -51
  56. package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
  57. package/dist/timecampus/pages/projects/ProjectsPage.js +354 -87
  58. package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
  59. package/dist/timecampus/pages/projects/TasksPage.js +367 -65
  60. package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
  61. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +674 -104
  62. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
  63. package/dist/timecampus/pages/team/TeamAnalyticsPage.js +46 -34
  64. package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
  65. package/dist/timecampus/pages/team/TeamMembersPage.js +93 -69
  66. package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
  67. package/dist/timecampus/pages/team/TeamPage.js +4 -3
  68. package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
  69. package/dist/timecampus/pages/templates/TemplateDetailPage.js +72 -53
  70. package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
  71. package/dist/timecampus/pages/templates/TemplatesPage.js +40 -35
  72. package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
  73. package/dist/timecampus/pages/time/TimeEntriesPage.js +484 -97
  74. package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
  75. package/dist/timecampus/pages/time/TimeTrackingPage.js +210 -99
  76. package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
  77. package/dist/timecampus/pages/time/TimerPage.js +229 -97
  78. package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
  79. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +315 -110
  80. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
  81. package/dist/timecampus/pages/timeline/TimelinePage.js +250 -0
  82. package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -0
  83. package/dist/timecampus/pages/widgets/WidgetDetailPage.js +158 -76
  84. package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
  85. package/dist/timecampus/pages/widgets/WidgetsPage.js +181 -52
  86. package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
  87. package/dist/timecampus/pages/workflows/WorkflowsPage.js +212 -61
  88. package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
  89. package/dist/timecampus/store/timecampusStore.js +13 -1
  90. package/dist/timecampus/store/timecampusStore.js.map +1 -1
  91. package/dist/timecampus/utils/helpers.js +20 -10
  92. package/dist/timecampus/utils/helpers.js.map +1 -1
  93. package/package.json +1 -1
@@ -1,134 +1,339 @@
1
- import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
2
- import { formatDate as t, formatHours as n, getInitials as r, getStatusColor as i } from "../../utils/helpers.js";
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";
1
+ import { formatDate as e, formatHours as t, getInitials as n } from "../../utils/helpers.js";
2
+ import { StatCard as r } from "../../components/StatCard.js";
3
+ import { LoadingSkeleton as i } from "../../components/LoadingSkeleton.js";
4
+ import { useApproveRequestMutation as a, useGetApprovalStatsQuery as o, useListApprovalRequestsQuery as ee, useRejectRequestMutation as te, useRequestRevisionMutation as ne, useWithdrawRequestMutation as s } from "../../../generated/wspace-operations.js";
5
+ import { PageLayout as c } from "../../components/PageLayout.js";
6
+ import { EmptyState as l } from "../../components/EmptyState.js";
7
+ import { ApprovalStatus as u } from "../../../generated/wspace-types.js";
8
+ import { Modal as d } from "../../components/Modal.js";
9
+ import { StatusBadge as re } from "../../components/StatusBadge.js";
10
+ import { ToastContainer as ie, useToast as ae } from "../../components/Toast.js";
11
+ import { useState as f } from "react";
12
+ import { CheckCircle as p, Clock as oe, MessageSquare as se, Undo2 as ce, XCircle as m } from "lucide-react";
13
+ import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
10
14
  //#region src/timecampus/pages/time-approval/TimeApprovalPage.tsx
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: {
16
- id: e,
17
- reason: "Rejected by reviewer"
18
- } });
15
+ var v = 25, y = "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";
16
+ function b() {
17
+ let { toasts: b, addToast: x, dismissToast: S } = ae(), { data: C, loading: w, error: T, refetch: le, fetchMore: ue } = ee({
18
+ variables: {
19
+ limit: v,
20
+ offset: 0
21
+ },
22
+ notifyOnNetworkStatusChange: !0
23
+ }), { data: de, loading: E, error: D, refetch: O } = o(), [k] = a(), [A, { loading: j }] = te(), [M, { loading: N }] = ne(), [P] = s(), F = C?.approvalRequests?.items ?? [], I = C?.approvalRequests?.total ?? 0, L = C?.approvalRequests?.hasMore ?? !1, R = de?.approvalStats, [z, B] = f(!1), [V, H] = f(null), [U, W] = f(null), [G, K] = f(""), [q, J] = f(null), [Y, X] = f(""), [Z, Q] = f(null), $ = async () => {
24
+ await Promise.all([le(), O()]);
25
+ }, fe = async (e) => {
26
+ H(e);
27
+ try {
28
+ await k({ variables: { id: e } }), x({
29
+ title: "Timesheet approved",
30
+ variant: "success"
31
+ }), await $();
32
+ } catch {
33
+ x({
34
+ title: "Failed to approve timesheet",
35
+ variant: "error"
36
+ });
37
+ } finally {
38
+ H(null);
39
+ }
19
40
  };
20
- return b && C ? /* @__PURE__ */ g(d, {
41
+ return w && E && F.length === 0 ? /* @__PURE__ */ g(c, {
21
42
  title: "Time Approval",
22
43
  description: "Review and approve team timesheets",
23
- children: /* @__PURE__ */ g(o, {
44
+ children: /* @__PURE__ */ g(i, {
24
45
  variant: "list",
25
46
  count: 4
26
47
  })
27
- }) : x && w ? /* @__PURE__ */ g(d, {
48
+ }) : T && D ? /* @__PURE__ */ g(c, {
28
49
  title: "Time Approval",
29
50
  description: "Review and approve team timesheets",
30
51
  children: /* @__PURE__ */ g("div", {
31
52
  className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
32
53
  children: "Failed to load approval data. Please try again later."
33
54
  })
34
- }) : /* @__PURE__ */ _(d, {
55
+ }) : /* @__PURE__ */ _(c, {
35
56
  title: "Time Approval",
36
57
  description: "Review and approve team timesheets",
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", {
39
- className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
40
- children: [
41
- /* @__PURE__ */ g(a, {
42
- featured: !0,
43
- title: "Pending Approvals",
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"
47
- }),
48
- /* @__PURE__ */ g(a, {
49
- title: "Approved This Week",
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"
53
- }),
54
- /* @__PURE__ */ g(a, {
55
- title: "Rejected",
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"
59
- }),
60
- /* @__PURE__ */ g(a, {
61
- title: "Avg. Review Time",
62
- value: A?.averageReviewTime ? `${Math.round(A.averageReviewTime)}h` : "N/A",
63
- description: "Target: 24h"
64
- })
65
- ]
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",
72
- children: /* @__PURE__ */ g("div", {
73
- className: "divide-y divide-border",
74
- children: k.map((e) => /* @__PURE__ */ _("div", {
75
- className: "flex items-center justify-between p-4 hover:bg-bg-sunken/50 transition-colors",
76
- children: [/* @__PURE__ */ _("div", {
77
- className: "flex items-center gap-3",
78
- children: [/* @__PURE__ */ g("div", {
79
- className: "flex h-9 w-9 items-center justify-center rounded-full bg-action-primary-bg/10 text-sm font-medium text-primary",
80
- children: r(e.userId)
81
- }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
82
- className: "text-sm font-medium text-text-primary",
83
- children: e.userId
84
- }), /* @__PURE__ */ _("p", {
85
- className: "text-xs text-text-secondary",
86
- children: [
87
- e.period,
88
- " · Submitted",
89
- " ",
90
- t(e.submittedAt)
91
- ]
92
- })] })]
93
- }), /* @__PURE__ */ _("div", {
94
- className: "flex items-center gap-4",
95
- children: [
96
- /* @__PURE__ */ _("div", {
97
- className: "text-right",
98
- children: [/* @__PURE__ */ g("p", {
58
+ purpose: "The reviewer's queue for signing off team timesheets before they're billed or paid — approve, reject or request changes in one place so payroll and invoices go out accurate and on time.",
59
+ children: [
60
+ /* @__PURE__ */ _("div", {
61
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
62
+ children: [
63
+ /* @__PURE__ */ g(r, {
64
+ featured: !0,
65
+ title: "Pending Approvals",
66
+ value: String(R?.pendingCount ?? 0),
67
+ icon: /* @__PURE__ */ g(oe, { className: "h-5 w-5" }),
68
+ iconBgColor: "bg-status-warning-bg text-status-warning-text"
69
+ }),
70
+ /* @__PURE__ */ g(r, {
71
+ title: "Approved This Week",
72
+ value: String(R?.approvedCount ?? 0),
73
+ icon: /* @__PURE__ */ g(p, { className: "h-5 w-5" }),
74
+ iconBgColor: "bg-status-success-bg text-status-success-text"
75
+ }),
76
+ /* @__PURE__ */ g(r, {
77
+ title: "Rejected",
78
+ value: String(R?.rejectedCount ?? 0),
79
+ icon: /* @__PURE__ */ g(m, { className: "h-5 w-5" }),
80
+ iconBgColor: "bg-status-error-bg text-status-error-text"
81
+ }),
82
+ /* @__PURE__ */ g(r, {
83
+ title: "Avg. Review Time",
84
+ value: R?.averageReviewTime ? `${Math.round(R.averageReviewTime)}h` : "N/A",
85
+ description: "Target: 24h"
86
+ })
87
+ ]
88
+ }),
89
+ F.length === 0 && !w ? /* @__PURE__ */ g(l, {
90
+ illustration: "empty-notifications",
91
+ title: "No approval requests found",
92
+ description: "You're all caught up — submitted timesheets waiting on your review will appear here."
93
+ }) : /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("div", {
94
+ className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop",
95
+ children: /* @__PURE__ */ g("div", {
96
+ className: "divide-y divide-border",
97
+ children: F.map((r) => {
98
+ let i = r.status === u.Pending, a = V === r.id;
99
+ return /* @__PURE__ */ _("div", {
100
+ className: "flex flex-col gap-3 p-4 hover:bg-bg-sunken/50 transition-colors sm:flex-row sm:items-center sm:justify-between",
101
+ children: [/* @__PURE__ */ _("div", {
102
+ className: "flex items-center gap-3",
103
+ children: [/* @__PURE__ */ g("div", {
104
+ className: "flex h-9 w-9 items-center justify-center rounded-full bg-action-primary-bg/10 text-sm font-medium text-primary",
105
+ children: n(r.userId)
106
+ }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
99
107
  className: "text-sm font-medium text-text-primary",
100
- children: n(e.totalHours)
108
+ children: r.userId
101
109
  }), /* @__PURE__ */ _("p", {
102
110
  className: "text-xs text-text-secondary",
103
- children: [n(e.billableHours), " billable"]
104
- })]
105
- }),
106
- /* @__PURE__ */ g("span", {
107
- className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${i(e.status)}`,
108
- children: e.status
109
- }),
110
- e.status === "PENDING" && /* @__PURE__ */ _("div", {
111
- className: "flex items-center gap-1",
112
- children: [/* @__PURE__ */ g("button", {
113
- onClick: () => M(e.id),
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" })
122
- })]
123
- })
124
- ]
125
- })]
126
- }, e.id))
111
+ children: [
112
+ r.period,
113
+ " · Submitted",
114
+ " ",
115
+ e(r.submittedAt)
116
+ ]
117
+ })] })]
118
+ }), /* @__PURE__ */ _("div", {
119
+ className: "flex flex-wrap items-center gap-3",
120
+ children: [
121
+ /* @__PURE__ */ _("div", {
122
+ className: "text-right",
123
+ children: [/* @__PURE__ */ g("p", {
124
+ className: "text-sm font-medium text-text-primary",
125
+ children: t(r.totalHours)
126
+ }), /* @__PURE__ */ _("p", {
127
+ className: "text-xs text-text-secondary",
128
+ children: [t(r.billableHours), " billable"]
129
+ })]
130
+ }),
131
+ /* @__PURE__ */ g(re, {
132
+ label: r.status.toLowerCase(),
133
+ status: r.status.toLowerCase()
134
+ }),
135
+ i && /* @__PURE__ */ _("div", {
136
+ className: "flex items-center gap-1",
137
+ children: [
138
+ /* @__PURE__ */ g("button", {
139
+ onClick: () => fe(r.id),
140
+ disabled: a,
141
+ title: "Approve",
142
+ className: "rounded-md p-1.5 text-status-success-text hover:bg-status-success-bg disabled:opacity-50",
143
+ children: /* @__PURE__ */ g(p, { className: "h-4 w-4" })
144
+ }),
145
+ /* @__PURE__ */ g("button", {
146
+ onClick: () => {
147
+ J(r), X("");
148
+ },
149
+ disabled: a,
150
+ title: "Request revision",
151
+ className: "rounded-md p-1.5 text-status-warning-text hover:bg-status-warning-bg disabled:opacity-50",
152
+ children: /* @__PURE__ */ g(se, { className: "h-4 w-4" })
153
+ }),
154
+ /* @__PURE__ */ g("button", {
155
+ onClick: () => {
156
+ W(r), K("");
157
+ },
158
+ disabled: a,
159
+ title: "Reject",
160
+ className: "rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg disabled:opacity-50",
161
+ children: /* @__PURE__ */ g(m, { className: "h-4 w-4" })
162
+ }),
163
+ /* @__PURE__ */ g("button", {
164
+ onClick: () => Q(r),
165
+ disabled: a,
166
+ title: "Withdraw request",
167
+ className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken disabled:opacity-50",
168
+ children: /* @__PURE__ */ g(ce, { className: "h-4 w-4" })
169
+ })
170
+ ]
171
+ })
172
+ ]
173
+ })]
174
+ }, r.id);
175
+ })
176
+ })
177
+ }), /* @__PURE__ */ _("div", {
178
+ className: "mt-4 flex items-center justify-between",
179
+ children: [/* @__PURE__ */ _("p", {
180
+ className: "text-xs text-text-secondary",
181
+ children: [
182
+ "Showing ",
183
+ F.length,
184
+ " of ",
185
+ I
186
+ ]
187
+ }), L && /* @__PURE__ */ g("button", {
188
+ onClick: async () => {
189
+ B(!0);
190
+ try {
191
+ await ue({
192
+ variables: {
193
+ limit: v,
194
+ offset: F.length
195
+ },
196
+ updateQuery: (e, { fetchMoreResult: t }) => t ? {
197
+ ...e,
198
+ approvalRequests: {
199
+ ...t.approvalRequests,
200
+ items: [...e.approvalRequests.items, ...t.approvalRequests.items]
201
+ }
202
+ } : e
203
+ });
204
+ } finally {
205
+ B(!1);
206
+ }
207
+ },
208
+ disabled: z,
209
+ className: "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",
210
+ children: z ? "Loading..." : "Load more"
211
+ })]
212
+ })] }),
213
+ /* @__PURE__ */ _(d, {
214
+ open: U !== null,
215
+ onClose: () => W(null),
216
+ title: "Reject timesheet",
217
+ description: U ? `Rejecting ${U.userId}'s ${U.period} timesheet.` : void 0,
218
+ size: "sm",
219
+ footer: /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("button", {
220
+ onClick: () => W(null),
221
+ className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
222
+ children: "Cancel"
223
+ }), /* @__PURE__ */ g("button", {
224
+ onClick: async () => {
225
+ if (!(!U || !G.trim())) try {
226
+ await A({ variables: {
227
+ id: U.id,
228
+ reason: G.trim()
229
+ } }), x({
230
+ title: "Timesheet rejected",
231
+ variant: "success"
232
+ }), W(null), K(""), await $();
233
+ } catch {
234
+ x({
235
+ title: "Failed to reject timesheet",
236
+ variant: "error"
237
+ });
238
+ }
239
+ },
240
+ disabled: j || !G.trim(),
241
+ className: "rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50",
242
+ children: j ? "Rejecting..." : "Reject"
243
+ })] }),
244
+ children: [/* @__PURE__ */ g("label", {
245
+ className: "block text-sm font-medium text-text-primary",
246
+ children: "Rejection reason"
247
+ }), /* @__PURE__ */ g("textarea", {
248
+ rows: 3,
249
+ placeholder: "Explain why this timesheet is being rejected",
250
+ value: G,
251
+ onChange: (e) => K(e.target.value),
252
+ className: y
253
+ })]
254
+ }),
255
+ /* @__PURE__ */ _(d, {
256
+ open: q !== null,
257
+ onClose: () => J(null),
258
+ title: "Request revision",
259
+ description: q ? `Ask ${q.userId} to revise their ${q.period} timesheet.` : void 0,
260
+ size: "sm",
261
+ footer: /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("button", {
262
+ onClick: () => J(null),
263
+ className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
264
+ children: "Cancel"
265
+ }), /* @__PURE__ */ g("button", {
266
+ onClick: async () => {
267
+ if (!(!q || !Y.trim())) try {
268
+ await M({ variables: {
269
+ id: q.id,
270
+ notes: Y.trim()
271
+ } }), x({
272
+ title: "Revision requested",
273
+ variant: "success"
274
+ }), J(null), X(""), await $();
275
+ } catch {
276
+ x({
277
+ title: "Failed to request revision",
278
+ variant: "error"
279
+ });
280
+ }
281
+ },
282
+ disabled: N || !Y.trim(),
283
+ 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",
284
+ children: N ? "Sending..." : "Request Revision"
285
+ })] }),
286
+ children: [/* @__PURE__ */ g("label", {
287
+ className: "block text-sm font-medium text-text-primary",
288
+ children: "What needs changing?"
289
+ }), /* @__PURE__ */ g("textarea", {
290
+ rows: 3,
291
+ placeholder: "Describe the changes needed before this can be approved",
292
+ value: Y,
293
+ onChange: (e) => X(e.target.value),
294
+ className: y
295
+ })]
296
+ }),
297
+ /* @__PURE__ */ g(d, {
298
+ open: Z !== null,
299
+ onClose: () => Q(null),
300
+ title: "Withdraw request?",
301
+ description: Z ? `${Z.userId}'s ${Z.period} timesheet will be pulled from the review queue.` : void 0,
302
+ size: "sm",
303
+ footer: /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("button", {
304
+ onClick: () => Q(null),
305
+ className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
306
+ children: "Cancel"
307
+ }), /* @__PURE__ */ g("button", {
308
+ onClick: async () => {
309
+ if (Z) try {
310
+ await P({ variables: { id: Z.id } }), x({
311
+ title: "Request withdrawn",
312
+ variant: "success"
313
+ }), Q(null), await $();
314
+ } catch {
315
+ x({
316
+ title: "Failed to withdraw request",
317
+ variant: "error"
318
+ });
319
+ }
320
+ },
321
+ 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",
322
+ children: "Withdraw"
323
+ })] }),
324
+ children: /* @__PURE__ */ g("p", {
325
+ className: "text-sm text-text-secondary",
326
+ children: "Withdrawing removes the request from the approval queue. It can be resubmitted later."
327
+ })
328
+ }),
329
+ /* @__PURE__ */ g(ie, {
330
+ toasts: b,
331
+ onDismiss: S
127
332
  })
128
- })]
333
+ ]
129
334
  });
130
335
  }
131
336
  //#endregion
132
- export { v as TimeApprovalPage };
337
+ export { b as TimeApprovalPage };
133
338
 
134
339
  //# 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 { 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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"}
1
+ {"version":3,"file":"TimeApprovalPage.js","names":[],"sources":["../../../../src/timecampus/pages/time-approval/TimeApprovalPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n CheckCircle,\n XCircle,\n Clock,\n MessageSquare,\n Undo2,\n} from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { Modal } from \"../../components/Modal\";\nimport { ToastContainer, useToast } from \"../../components/Toast\";\nimport { getInitials, formatHours, formatDate } from \"../../utils/helpers\";\nimport {\n useListApprovalRequestsQuery,\n useGetApprovalStatsQuery,\n useApproveRequestMutation,\n useRejectRequestMutation,\n useRequestRevisionMutation,\n useWithdrawRequestMutation,\n type ApprovalRequestFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { ApprovalStatus } from \"@/generated/wspace-types\";\n\nconst PAGE_SIZE = 25;\n\nconst inputClass =\n \"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\nexport function TimeApprovalPage() {\n const { toasts, addToast, dismissToast } = useToast();\n\n const {\n data: requestsData,\n loading: requestsLoading,\n error: requestsError,\n refetch,\n fetchMore,\n } = useListApprovalRequestsQuery({\n variables: { limit: PAGE_SIZE, offset: 0 },\n notifyOnNetworkStatusChange: true,\n });\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n refetch: refetchStats,\n } = useGetApprovalStatsQuery();\n\n const [approveRequest] = useApproveRequestMutation();\n const [rejectRequest, { loading: rejecting }] = useRejectRequestMutation();\n const [requestRevision, { loading: requestingRevision }] =\n useRequestRevisionMutation();\n const [withdrawRequest] = useWithdrawRequestMutation();\n\n const requests = requestsData?.approvalRequests?.items ?? [];\n const total = requestsData?.approvalRequests?.total ?? 0;\n const hasMore = requestsData?.approvalRequests?.hasMore ?? false;\n const stats = statsData?.approvalStats;\n\n const [loadingMore, setLoadingMore] = useState(false);\n const [busyId, setBusyId] = useState<string | null>(null);\n\n // Reject reason modal\n const [rejectTarget, setRejectTarget] =\n useState<ApprovalRequestFieldsFragment | null>(null);\n const [rejectReason, setRejectReason] = useState(\"\");\n\n // Request revision modal\n const [revisionTarget, setRevisionTarget] =\n useState<ApprovalRequestFieldsFragment | null>(null);\n const [revisionNotes, setRevisionNotes] = useState(\"\");\n\n // Withdraw confirm modal\n const [withdrawTarget, setWithdrawTarget] =\n useState<ApprovalRequestFieldsFragment | null>(null);\n\n const refreshAll = async () => {\n await Promise.all([refetch(), refetchStats()]);\n };\n\n const handleApprove = async (id: string) => {\n setBusyId(id);\n try {\n await approveRequest({ variables: { id } });\n addToast({ title: \"Timesheet approved\", variant: \"success\" });\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to approve timesheet\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleReject = async () => {\n if (!rejectTarget || !rejectReason.trim()) return;\n try {\n await rejectRequest({\n variables: { id: rejectTarget.id, reason: rejectReason.trim() },\n });\n addToast({ title: \"Timesheet rejected\", variant: \"success\" });\n setRejectTarget(null);\n setRejectReason(\"\");\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to reject timesheet\", variant: \"error\" });\n }\n };\n\n const handleRequestRevision = async () => {\n if (!revisionTarget || !revisionNotes.trim()) return;\n try {\n await requestRevision({\n variables: { id: revisionTarget.id, notes: revisionNotes.trim() },\n });\n addToast({ title: \"Revision requested\", variant: \"success\" });\n setRevisionTarget(null);\n setRevisionNotes(\"\");\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to request revision\", variant: \"error\" });\n }\n };\n\n const handleWithdraw = async () => {\n if (!withdrawTarget) return;\n try {\n await withdrawRequest({ variables: { id: withdrawTarget.id } });\n addToast({ title: \"Request withdrawn\", variant: \"success\" });\n setWithdrawTarget(null);\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to withdraw request\", variant: \"error\" });\n }\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { limit: PAGE_SIZE, offset: requests.length },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult) return prev;\n return {\n ...prev,\n approvalRequests: {\n ...fetchMoreResult.approvalRequests,\n items: [\n ...prev.approvalRequests.items,\n ...fetchMoreResult.approvalRequests.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n if (requestsLoading && statsLoading && requests.length === 0) {\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, reject or request changes 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 <>\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 const isPending = request.status === ApprovalStatus.Pending;\n const rowBusy = busyId === request.id;\n return (\n <div\n key={request.id}\n className=\"flex flex-col gap-3 p-4 hover:bg-bg-sunken/50 transition-colors sm:flex-row sm:items-center sm:justify-between\"\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 flex-wrap items-center gap-3\">\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 <StatusBadge\n label={request.status.toLowerCase()}\n status={request.status.toLowerCase()}\n />\n {isPending && (\n <div className=\"flex items-center gap-1\">\n <button\n onClick={() => handleApprove(request.id)}\n disabled={rowBusy}\n title=\"Approve\"\n className=\"rounded-md p-1.5 text-status-success-text hover:bg-status-success-bg disabled:opacity-50\"\n >\n <CheckCircle className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => {\n setRevisionTarget(request);\n setRevisionNotes(\"\");\n }}\n disabled={rowBusy}\n title=\"Request revision\"\n className=\"rounded-md p-1.5 text-status-warning-text hover:bg-status-warning-bg disabled:opacity-50\"\n >\n <MessageSquare className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => {\n setRejectTarget(request);\n setRejectReason(\"\");\n }}\n disabled={rowBusy}\n title=\"Reject\"\n className=\"rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg disabled:opacity-50\"\n >\n <XCircle className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setWithdrawTarget(request)}\n disabled={rowBusy}\n title=\"Withdraw request\"\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Undo2 className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n\n <div className=\"mt-4 flex items-center justify-between\">\n <p className=\"text-xs text-text-secondary\">\n Showing {requests.length} of {total}\n </p>\n {hasMore && (\n <button\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"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 <Modal\n open={rejectTarget !== null}\n onClose={() => setRejectTarget(null)}\n title=\"Reject timesheet\"\n description={\n rejectTarget\n ? `Rejecting ${rejectTarget.userId}'s ${rejectTarget.period} timesheet.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRejectTarget(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={handleReject}\n disabled={rejecting || !rejectReason.trim()}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50\"\n >\n {rejecting ? \"Rejecting...\" : \"Reject\"}\n </button>\n </>\n }\n >\n <label className=\"block text-sm font-medium text-text-primary\">\n Rejection reason\n </label>\n <textarea\n rows={3}\n placeholder=\"Explain why this timesheet is being rejected\"\n value={rejectReason}\n onChange={(e) => setRejectReason(e.target.value)}\n className={inputClass}\n />\n </Modal>\n\n <Modal\n open={revisionTarget !== null}\n onClose={() => setRevisionTarget(null)}\n title=\"Request revision\"\n description={\n revisionTarget\n ? `Ask ${revisionTarget.userId} to revise their ${revisionTarget.period} timesheet.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRevisionTarget(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={handleRequestRevision}\n disabled={requestingRevision || !revisionNotes.trim()}\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 {requestingRevision ? \"Sending...\" : \"Request Revision\"}\n </button>\n </>\n }\n >\n <label className=\"block text-sm font-medium text-text-primary\">\n What needs changing?\n </label>\n <textarea\n rows={3}\n placeholder=\"Describe the changes needed before this can be approved\"\n value={revisionNotes}\n onChange={(e) => setRevisionNotes(e.target.value)}\n className={inputClass}\n />\n </Modal>\n\n <Modal\n open={withdrawTarget !== null}\n onClose={() => setWithdrawTarget(null)}\n title=\"Withdraw request?\"\n description={\n withdrawTarget\n ? `${withdrawTarget.userId}'s ${withdrawTarget.period} timesheet will be pulled from the review queue.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setWithdrawTarget(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={handleWithdraw}\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 Withdraw\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Withdrawing removes the request from the approval queue. It can be\n resubmitted later.\n </p>\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}