@burdenoff/microfe-timecampus 2026.626.2 → 2026.703.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/wspace-operations.js +996 -816
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js +28 -0
- package/dist/generated/wspace-types.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/timecampus/TimeCampusRoutes.js +16 -12
- package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
- package/dist/timecampus/components/Modal.js +65 -0
- package/dist/timecampus/components/Modal.js.map +1 -0
- package/dist/timecampus/components/StatusBadge.js +28 -0
- package/dist/timecampus/components/StatusBadge.js.map +1 -0
- package/dist/timecampus/components/Toast.js +87 -0
- package/dist/timecampus/components/Toast.js.map +1 -0
- package/dist/timecampus/index.js +1 -1
- package/dist/timecampus/pages/HomePage.js +143 -135
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/applets/AppletStorePage.js +14 -19
- package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js +49 -37
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +315 -87
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/channels/ChannelsPage.js +340 -46
- package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +374 -92
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js +70 -37
- package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +366 -60
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +461 -87
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/geofencing/GeofencingPage.js +314 -55
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +30 -27
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +269 -74
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js +127 -0
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -0
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js +62 -52
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +361 -74
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingFormModal.js +156 -0
- package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -0
- package/dist/timecampus/pages/meetings/MeetingsPage.js +175 -74
- package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js +105 -0
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -0
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js +307 -67
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectCreatePage.js +51 -48
- package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectDetailPage.js +248 -51
- package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectsPage.js +354 -87
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js +367 -65
- package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +674 -104
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js +46 -34
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js +93 -69
- package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamPage.js +4 -3
- package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
- package/dist/timecampus/pages/templates/TemplateDetailPage.js +72 -53
- package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
- package/dist/timecampus/pages/templates/TemplatesPage.js +40 -35
- package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeEntriesPage.js +484 -97
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +210 -99
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +229 -97
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +315 -110
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js +250 -0
- package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -0
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js +158 -76
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
- package/dist/timecampus/pages/widgets/WidgetsPage.js +181 -52
- package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
- package/dist/timecampus/pages/workflows/WorkflowsPage.js +212 -61
- package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
- package/dist/timecampus/store/timecampusStore.js +13 -1
- package/dist/timecampus/store/timecampusStore.js.map +1 -1
- package/dist/timecampus/utils/helpers.js +20 -10
- package/dist/timecampus/utils/helpers.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,83 +1,385 @@
|
|
|
1
|
-
import { getPriorityColor as e
|
|
2
|
-
import { LoadingSkeleton as
|
|
3
|
-
import {
|
|
4
|
-
import { PageLayout as
|
|
5
|
-
import { EmptyState as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
1
|
+
import { getPriorityColor as e } from "../../utils/helpers.js";
|
|
2
|
+
import { LoadingSkeleton as t } from "../../components/LoadingSkeleton.js";
|
|
3
|
+
import { useCreateTaskMutation as n, useDeleteTaskMutation as r, useListTasksQuery as i, useReorderTasksMutation as a, useUpdateTaskMutation as o, useUpdateTaskStatusMutation as s } from "../../../generated/wspace-operations.js";
|
|
4
|
+
import { PageLayout as c } from "../../components/PageLayout.js";
|
|
5
|
+
import { EmptyState as l } from "../../components/EmptyState.js";
|
|
6
|
+
import { Modal as u } from "../../components/Modal.js";
|
|
7
|
+
import { useState as d } from "react";
|
|
8
|
+
import { ArrowDown as f, ArrowUp as ee, CheckSquare as te, Pencil as p, Plus as m, Trash2 as h } from "lucide-react";
|
|
9
|
+
import { useParams as g } from "react-router-dom";
|
|
10
|
+
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
11
|
+
import { GlassCard as b } from "@burdenoff/fe-libs/ui";
|
|
10
12
|
//#region src/timecampus/pages/projects/TasksPage.tsx
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
var x = 25, S = [
|
|
14
|
+
"todo",
|
|
15
|
+
"in-progress",
|
|
16
|
+
"review",
|
|
17
|
+
"done"
|
|
18
|
+
], ne = [
|
|
19
|
+
"low",
|
|
20
|
+
"medium",
|
|
21
|
+
"high",
|
|
22
|
+
"urgent"
|
|
23
|
+
], C = {
|
|
24
|
+
title: "",
|
|
25
|
+
description: "",
|
|
26
|
+
status: "todo",
|
|
27
|
+
priority: "medium",
|
|
28
|
+
assigneeId: "",
|
|
29
|
+
estimatedHours: "",
|
|
30
|
+
dueDate: ""
|
|
31
|
+
}, w = "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", T = "block text-sm font-medium text-text-primary";
|
|
32
|
+
function E() {
|
|
33
|
+
let { projectId: E } = g(), [D, re] = d(x), { data: O, loading: k, error: ie, refetch: A } = i({
|
|
34
|
+
variables: {
|
|
35
|
+
projectId: E,
|
|
36
|
+
limit: D,
|
|
37
|
+
offset: 0
|
|
38
|
+
},
|
|
39
|
+
skip: !E,
|
|
40
|
+
notifyOnNetworkStatusChange: !0
|
|
41
|
+
}), j = O?.tasks?.items ?? [], M = O?.tasks?.hasMore ?? !1, [N, P] = d(!1), [F, I] = d(null), [L, R] = d(null), [z, B] = d(C), [V, { loading: H, error: U }] = n(), [W, { loading: G, error: K }] = o(), [q] = s(), [J, { loading: Y, error: ae }] = r(), [oe, { loading: X }] = a(), Z = () => {
|
|
42
|
+
B(C), P(!0);
|
|
43
|
+
}, se = (e) => {
|
|
44
|
+
B({
|
|
45
|
+
title: e.title,
|
|
46
|
+
description: e.description ?? "",
|
|
47
|
+
status: e.status,
|
|
48
|
+
priority: e.priority,
|
|
49
|
+
assigneeId: e.assigneeId ?? "",
|
|
50
|
+
estimatedHours: e.estimatedHours == null ? "" : String(e.estimatedHours),
|
|
51
|
+
dueDate: e.dueDate ? e.dueDate.slice(0, 10) : ""
|
|
52
|
+
}), I(e);
|
|
53
|
+
}, ce = async () => {
|
|
54
|
+
!E || !z.title.trim() || (await V({ variables: { input: {
|
|
55
|
+
projectId: E,
|
|
56
|
+
title: z.title.trim(),
|
|
57
|
+
description: z.description.trim() || void 0,
|
|
58
|
+
status: z.status,
|
|
59
|
+
priority: z.priority,
|
|
60
|
+
assigneeId: z.assigneeId.trim() || void 0,
|
|
61
|
+
estimatedHours: z.estimatedHours ? Number(z.estimatedHours) : void 0,
|
|
62
|
+
dueDate: z.dueDate ? new Date(z.dueDate).toISOString() : void 0
|
|
63
|
+
} } }), P(!1), await A());
|
|
64
|
+
}, le = async () => {
|
|
65
|
+
!F || !z.title.trim() || (await W({ variables: {
|
|
66
|
+
id: F.id,
|
|
67
|
+
input: {
|
|
68
|
+
title: z.title.trim(),
|
|
69
|
+
description: z.description.trim() || void 0,
|
|
70
|
+
status: z.status,
|
|
71
|
+
priority: z.priority,
|
|
72
|
+
assigneeId: z.assigneeId.trim() || void 0,
|
|
73
|
+
estimatedHours: z.estimatedHours ? Number(z.estimatedHours) : void 0,
|
|
74
|
+
dueDate: z.dueDate ? new Date(z.dueDate).toISOString() : void 0
|
|
75
|
+
}
|
|
76
|
+
} }), I(null), await A());
|
|
77
|
+
}, ue = async (e, t) => {
|
|
78
|
+
await q({ variables: {
|
|
79
|
+
id: e.id,
|
|
80
|
+
status: t
|
|
81
|
+
} }), await A();
|
|
82
|
+
}, de = async () => {
|
|
83
|
+
L && (await J({ variables: { id: L.id } }), R(null), await A());
|
|
84
|
+
}, Q = async (e, t) => {
|
|
85
|
+
if (!E) return;
|
|
86
|
+
let n = e + t;
|
|
87
|
+
if (n < 0 || n >= j.length) return;
|
|
88
|
+
let r = j.map((e) => e.id), [i] = r.splice(e, 1);
|
|
89
|
+
r.splice(n, 0, i), await oe({ variables: {
|
|
90
|
+
projectId: E,
|
|
91
|
+
taskIds: r
|
|
92
|
+
} }), await A();
|
|
93
|
+
}, $ = /* @__PURE__ */ y("div", {
|
|
94
|
+
className: "space-y-4",
|
|
95
|
+
children: [
|
|
96
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
97
|
+
className: T,
|
|
98
|
+
children: "Title"
|
|
99
|
+
}), /* @__PURE__ */ v("input", {
|
|
100
|
+
type: "text",
|
|
101
|
+
placeholder: "Task title",
|
|
102
|
+
value: z.title,
|
|
103
|
+
onChange: (e) => B({
|
|
104
|
+
...z,
|
|
105
|
+
title: e.target.value
|
|
106
|
+
}),
|
|
107
|
+
className: w
|
|
108
|
+
})] }),
|
|
109
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
110
|
+
className: T,
|
|
111
|
+
children: "Description"
|
|
112
|
+
}), /* @__PURE__ */ v("textarea", {
|
|
113
|
+
placeholder: "Task description",
|
|
114
|
+
rows: 3,
|
|
115
|
+
value: z.description,
|
|
116
|
+
onChange: (e) => B({
|
|
117
|
+
...z,
|
|
118
|
+
description: e.target.value
|
|
119
|
+
}),
|
|
120
|
+
className: w
|
|
121
|
+
})] }),
|
|
122
|
+
/* @__PURE__ */ y("div", {
|
|
123
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
124
|
+
children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
125
|
+
className: T,
|
|
126
|
+
children: "Status"
|
|
127
|
+
}), /* @__PURE__ */ v("select", {
|
|
128
|
+
value: z.status,
|
|
129
|
+
onChange: (e) => B({
|
|
130
|
+
...z,
|
|
131
|
+
status: e.target.value
|
|
132
|
+
}),
|
|
133
|
+
className: w,
|
|
134
|
+
children: S.map((e) => /* @__PURE__ */ v("option", {
|
|
135
|
+
value: e,
|
|
136
|
+
children: e
|
|
137
|
+
}, e))
|
|
138
|
+
})] }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
139
|
+
className: T,
|
|
140
|
+
children: "Priority"
|
|
141
|
+
}), /* @__PURE__ */ v("select", {
|
|
142
|
+
value: z.priority,
|
|
143
|
+
onChange: (e) => B({
|
|
144
|
+
...z,
|
|
145
|
+
priority: e.target.value
|
|
146
|
+
}),
|
|
147
|
+
className: w,
|
|
148
|
+
children: ne.map((e) => /* @__PURE__ */ v("option", {
|
|
149
|
+
value: e,
|
|
150
|
+
children: e
|
|
151
|
+
}, e))
|
|
152
|
+
})] })]
|
|
153
|
+
}),
|
|
154
|
+
/* @__PURE__ */ y("div", {
|
|
155
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-3",
|
|
156
|
+
children: [
|
|
157
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
158
|
+
className: T,
|
|
159
|
+
children: "Assignee ID"
|
|
160
|
+
}), /* @__PURE__ */ v("input", {
|
|
161
|
+
type: "text",
|
|
162
|
+
placeholder: "User ID",
|
|
163
|
+
value: z.assigneeId,
|
|
164
|
+
onChange: (e) => B({
|
|
165
|
+
...z,
|
|
166
|
+
assigneeId: e.target.value
|
|
167
|
+
}),
|
|
168
|
+
className: w
|
|
169
|
+
})] }),
|
|
170
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
171
|
+
className: T,
|
|
172
|
+
children: "Estimated Hours"
|
|
173
|
+
}), /* @__PURE__ */ v("input", {
|
|
174
|
+
type: "number",
|
|
175
|
+
placeholder: "0",
|
|
176
|
+
value: z.estimatedHours,
|
|
177
|
+
onChange: (e) => B({
|
|
178
|
+
...z,
|
|
179
|
+
estimatedHours: e.target.value
|
|
180
|
+
}),
|
|
181
|
+
className: w
|
|
182
|
+
})] }),
|
|
183
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
184
|
+
className: T,
|
|
185
|
+
children: "Due Date"
|
|
186
|
+
}), /* @__PURE__ */ v("input", {
|
|
187
|
+
type: "date",
|
|
188
|
+
value: z.dueDate,
|
|
189
|
+
onChange: (e) => B({
|
|
190
|
+
...z,
|
|
191
|
+
dueDate: e.target.value
|
|
192
|
+
}),
|
|
193
|
+
className: w
|
|
194
|
+
})] })
|
|
195
|
+
]
|
|
196
|
+
})
|
|
197
|
+
]
|
|
198
|
+
});
|
|
199
|
+
return ie ? /* @__PURE__ */ v(c, {
|
|
17
200
|
title: "Tasks",
|
|
18
|
-
description: `Tasks for project ${
|
|
19
|
-
children: /* @__PURE__ */
|
|
201
|
+
description: `Tasks for project ${E}`,
|
|
202
|
+
children: /* @__PURE__ */ v("div", {
|
|
20
203
|
className: "rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text",
|
|
21
204
|
children: "Failed to load tasks. Please try again later."
|
|
22
205
|
})
|
|
23
|
-
}) : /* @__PURE__ */
|
|
206
|
+
}) : /* @__PURE__ */ y(c, {
|
|
24
207
|
title: "Tasks",
|
|
25
|
-
description: `Tasks for project ${
|
|
208
|
+
description: `Tasks for project ${E}`,
|
|
26
209
|
purpose: "Break a project down into the concrete pieces of work that need doing, then track hours against each one — so you can see what's done, what's blocking, and where effort is actually being spent.",
|
|
27
|
-
actions: /* @__PURE__ */
|
|
210
|
+
actions: /* @__PURE__ */ y("button", {
|
|
211
|
+
onClick: Z,
|
|
28
212
|
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
29
|
-
children: [/* @__PURE__ */
|
|
213
|
+
children: [/* @__PURE__ */ v(m, { className: "h-4 w-4" }), "Add Task"]
|
|
30
214
|
}),
|
|
31
|
-
children:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
className: "
|
|
48
|
-
children:
|
|
49
|
-
className: "
|
|
50
|
-
children:
|
|
51
|
-
className: "flex items-center
|
|
52
|
-
children: [/* @__PURE__ */
|
|
53
|
-
className:
|
|
54
|
-
children:
|
|
55
|
-
|
|
56
|
-
|
|
215
|
+
children: [
|
|
216
|
+
k && j.length === 0 ? /* @__PURE__ */ v(t, {
|
|
217
|
+
variant: "list",
|
|
218
|
+
count: 5
|
|
219
|
+
}) : j.length === 0 ? /* @__PURE__ */ v(l, {
|
|
220
|
+
illustration: "empty-generic",
|
|
221
|
+
title: "No tasks yet",
|
|
222
|
+
description: "Add a task to break this project into trackable work.",
|
|
223
|
+
action: /* @__PURE__ */ y("button", {
|
|
224
|
+
onClick: Z,
|
|
225
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
226
|
+
children: [/* @__PURE__ */ v(m, { className: "h-4 w-4" }), "Add Task"]
|
|
227
|
+
})
|
|
228
|
+
}) : /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v(b, {
|
|
229
|
+
treatment: "glass",
|
|
230
|
+
glow: !0,
|
|
231
|
+
className: "overflow-hidden",
|
|
232
|
+
children: /* @__PURE__ */ v("div", {
|
|
233
|
+
className: "divide-y divide-border",
|
|
234
|
+
children: j.map((t, n) => /* @__PURE__ */ y("div", {
|
|
235
|
+
className: "flex flex-col gap-3 p-4 transition-colors hover:bg-bg-sunken/50 sm:flex-row sm:items-center sm:justify-between",
|
|
236
|
+
children: [/* @__PURE__ */ y("div", {
|
|
237
|
+
className: "flex items-center gap-3",
|
|
238
|
+
children: [/* @__PURE__ */ v(te, { className: `h-5 w-5 shrink-0 ${t.status === "done" ? "text-status-success-text" : "text-text-secondary"}` }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
239
|
+
className: `text-sm font-medium ${t.status === "done" ? "text-text-secondary line-through" : "text-text-primary"}`,
|
|
240
|
+
children: t.title
|
|
241
|
+
}), /* @__PURE__ */ y("p", {
|
|
242
|
+
className: "text-xs text-text-secondary",
|
|
243
|
+
children: [
|
|
244
|
+
t.assigneeId ?? "Unassigned",
|
|
245
|
+
" ·",
|
|
246
|
+
" ",
|
|
247
|
+
t.trackedHours,
|
|
248
|
+
"h tracked"
|
|
249
|
+
]
|
|
250
|
+
})] })]
|
|
251
|
+
}), /* @__PURE__ */ y("div", {
|
|
252
|
+
className: "flex flex-wrap items-center gap-2",
|
|
57
253
|
children: [
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
254
|
+
/* @__PURE__ */ v("span", {
|
|
255
|
+
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${e(t.priority)}`,
|
|
256
|
+
children: t.priority
|
|
257
|
+
}),
|
|
258
|
+
/* @__PURE__ */ v("select", {
|
|
259
|
+
value: t.status,
|
|
260
|
+
onChange: (e) => void ue(t, e.target.value),
|
|
261
|
+
className: "rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs font-medium text-text-primary",
|
|
262
|
+
"aria-label": "Task status",
|
|
263
|
+
children: S.map((e) => /* @__PURE__ */ v("option", {
|
|
264
|
+
value: e,
|
|
265
|
+
children: e
|
|
266
|
+
}, e))
|
|
267
|
+
}),
|
|
268
|
+
/* @__PURE__ */ v("button", {
|
|
269
|
+
onClick: () => void Q(n, -1),
|
|
270
|
+
disabled: n === 0 || X,
|
|
271
|
+
className: "rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-40",
|
|
272
|
+
"aria-label": "Move up",
|
|
273
|
+
children: /* @__PURE__ */ v(ee, { className: "h-4 w-4" })
|
|
274
|
+
}),
|
|
275
|
+
/* @__PURE__ */ v("button", {
|
|
276
|
+
onClick: () => void Q(n, 1),
|
|
277
|
+
disabled: n === j.length - 1 || X,
|
|
278
|
+
className: "rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-40",
|
|
279
|
+
"aria-label": "Move down",
|
|
280
|
+
children: /* @__PURE__ */ v(f, { className: "h-4 w-4" })
|
|
281
|
+
}),
|
|
282
|
+
/* @__PURE__ */ v("button", {
|
|
283
|
+
onClick: () => se(t),
|
|
284
|
+
className: "rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary",
|
|
285
|
+
"aria-label": "Edit task",
|
|
286
|
+
children: /* @__PURE__ */ v(p, { className: "h-4 w-4" })
|
|
287
|
+
}),
|
|
288
|
+
/* @__PURE__ */ v("button", {
|
|
289
|
+
onClick: () => R(t),
|
|
290
|
+
className: "rounded-md p-1 text-status-error-text hover:bg-status-error-bg",
|
|
291
|
+
"aria-label": "Delete task",
|
|
292
|
+
children: /* @__PURE__ */ v(h, { className: "h-4 w-4" })
|
|
293
|
+
})
|
|
63
294
|
]
|
|
64
|
-
})] })]
|
|
65
|
-
}), /* @__PURE__ */ u("div", {
|
|
66
|
-
className: "flex items-center gap-2",
|
|
67
|
-
children: [/* @__PURE__ */ l("span", {
|
|
68
|
-
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${e(n.priority)}`,
|
|
69
|
-
children: n.priority
|
|
70
|
-
}), /* @__PURE__ */ l("span", {
|
|
71
|
-
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${t(n.status)}`,
|
|
72
|
-
children: n.status
|
|
73
295
|
})]
|
|
74
|
-
})
|
|
75
|
-
}
|
|
296
|
+
}, t.id))
|
|
297
|
+
})
|
|
298
|
+
}), M && /* @__PURE__ */ v("div", {
|
|
299
|
+
className: "mt-6 flex justify-center",
|
|
300
|
+
children: /* @__PURE__ */ v("button", {
|
|
301
|
+
onClick: () => re((e) => e + x),
|
|
302
|
+
disabled: k,
|
|
303
|
+
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",
|
|
304
|
+
children: k ? "Loading…" : "Load more"
|
|
305
|
+
})
|
|
306
|
+
})] }),
|
|
307
|
+
/* @__PURE__ */ y(u, {
|
|
308
|
+
open: N,
|
|
309
|
+
onClose: () => P(!1),
|
|
310
|
+
title: "Add Task",
|
|
311
|
+
description: "Break this project into a trackable piece of work.",
|
|
312
|
+
size: "xl",
|
|
313
|
+
footer: /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("button", {
|
|
314
|
+
onClick: () => P(!1),
|
|
315
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
316
|
+
children: "Cancel"
|
|
317
|
+
}), /* @__PURE__ */ v("button", {
|
|
318
|
+
onClick: ce,
|
|
319
|
+
disabled: H || !z.title.trim(),
|
|
320
|
+
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",
|
|
321
|
+
children: H ? "Creating…" : "Create Task"
|
|
322
|
+
})] }),
|
|
323
|
+
children: [U && /* @__PURE__ */ v("div", {
|
|
324
|
+
className: "mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
|
|
325
|
+
children: "Failed to create task. Please try again."
|
|
326
|
+
}), $]
|
|
327
|
+
}),
|
|
328
|
+
/* @__PURE__ */ y(u, {
|
|
329
|
+
open: F !== null,
|
|
330
|
+
onClose: () => I(null),
|
|
331
|
+
title: "Edit Task",
|
|
332
|
+
size: "xl",
|
|
333
|
+
footer: /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("button", {
|
|
334
|
+
onClick: () => I(null),
|
|
335
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
336
|
+
children: "Cancel"
|
|
337
|
+
}), /* @__PURE__ */ v("button", {
|
|
338
|
+
onClick: le,
|
|
339
|
+
disabled: G || !z.title.trim(),
|
|
340
|
+
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",
|
|
341
|
+
children: G ? "Saving…" : "Save Changes"
|
|
342
|
+
})] }),
|
|
343
|
+
children: [K && /* @__PURE__ */ v("div", {
|
|
344
|
+
className: "mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
|
|
345
|
+
children: "Failed to update task. Please try again."
|
|
346
|
+
}), $]
|
|
347
|
+
}),
|
|
348
|
+
/* @__PURE__ */ y(u, {
|
|
349
|
+
open: L !== null,
|
|
350
|
+
onClose: () => R(null),
|
|
351
|
+
title: "Delete Task",
|
|
352
|
+
size: "sm",
|
|
353
|
+
footer: /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("button", {
|
|
354
|
+
onClick: () => R(null),
|
|
355
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
356
|
+
children: "Cancel"
|
|
357
|
+
}), /* @__PURE__ */ v("button", {
|
|
358
|
+
onClick: de,
|
|
359
|
+
disabled: Y,
|
|
360
|
+
className: "rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50",
|
|
361
|
+
children: Y ? "Deleting…" : "Delete"
|
|
362
|
+
})] }),
|
|
363
|
+
children: [ae && /* @__PURE__ */ v("div", {
|
|
364
|
+
className: "mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
|
|
365
|
+
children: "Failed to delete task. Please try again."
|
|
366
|
+
}), /* @__PURE__ */ y("p", {
|
|
367
|
+
className: "text-sm text-text-secondary",
|
|
368
|
+
children: [
|
|
369
|
+
"Are you sure you want to delete",
|
|
370
|
+
" ",
|
|
371
|
+
/* @__PURE__ */ v("span", {
|
|
372
|
+
className: "font-semibold text-text-primary",
|
|
373
|
+
children: L?.title
|
|
374
|
+
}),
|
|
375
|
+
"? This action cannot be undone."
|
|
376
|
+
]
|
|
377
|
+
})]
|
|
76
378
|
})
|
|
77
|
-
|
|
379
|
+
]
|
|
78
380
|
});
|
|
79
381
|
}
|
|
80
382
|
//#endregion
|
|
81
|
-
export {
|
|
383
|
+
export { E as TasksPage };
|
|
82
384
|
|
|
83
385
|
//# sourceMappingURL=TasksPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TasksPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/TasksPage.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { Plus, CheckSquare } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { getStatusColor, getPriorityColor } from \"../../utils/helpers\";\nimport {\n useListTasksQuery,\n type TaskFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nexport function TasksPage() {\n const { projectId } = useParams<{ projectId: string }>();\n\n const { data, loading, error } = useListTasksQuery({\n variables: { projectId: projectId! },\n skip: !projectId,\n });\n\n const tasks = data?.tasks?.items ?? [];\n\n if (error) {\n return (\n <PageLayout title=\"Tasks\" description={`Tasks for project ${projectId}`}>\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n Failed to load tasks. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Tasks\"\n description={`Tasks for project ${projectId}`}\n purpose=\"Break a project down into the concrete pieces of work that need doing, then track hours against each one — so you can see what's done, what's blocking, and where effort is actually being spent.\"\n actions={\n <button className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\">\n <Plus className=\"h-4 w-4\" />\n Add Task\n </button>\n }\n >\n {loading ? (\n <LoadingSkeleton variant=\"list\" count={5} />\n ) : tasks.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No tasks yet\"\n description=\"Add a task to break this project into trackable work.\"\n action={\n <button className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\">\n <Plus className=\"h-4 w-4\" />\n Add Task\n </button>\n }\n />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"divide-y divide-border\">\n {tasks.map((task: TaskFieldsFragment) => (\n <div\n key={task.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 <CheckSquare\n className={`h-5 w-5 ${task.status === \"done\" ? \"text-status-success-text\" : \"text-text-secondary\"}`}\n />\n <div>\n <p\n className={`text-sm font-medium ${task.status === \"done\" ? \"text-text-secondary line-through\" : \"text-text-primary\"}`}\n >\n {task.title}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {task.assigneeId ?? \"Unassigned\"} ·{\" \"}\n {task.trackedHours}h tracked\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getPriorityColor(task.priority)}`}\n >\n {task.priority}\n </span>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(task.status)}`}\n >\n {task.status}\n </span>\n </div>\n </div>\n ))}\n </div>\n </GlassCard>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;AAYA,SAAgB,IAAY;CAC1B,IAAM,EAAE,iBAAc,GAAkC,EAElD,EAAE,SAAM,YAAS,aAAU,EAAkB;EACjD,WAAW,EAAa,cAAY;EACpC,MAAM,CAAC;EACR,CAAC,EAEI,IAAQ,GAAM,OAAO,SAAS,EAAE;AAYtC,QAVI,IAEA,kBAAC,GAAD;EAAY,OAAM;EAAQ,aAAa,qBAAqB;YAC1D,kBAAC,OAAD;GAAK,WAAU;aAAoG;GAE7G,CAAA;EACK,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAa,qBAAqB;EAClC,SAAQ;EACR,SACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,WAErB;;YAGV,IACC,kBAAC,GAAD;GAAiB,SAAQ;GAAO,OAAO;GAAK,CAAA,GAC1C,EAAM,WAAW,IACnB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,WAErB;;GAEX,CAAA,GAEF,kBAAC,GAAD;GAAW,WAAU;GAAQ,MAAA;GAAK,WAAU;aAC1C,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAM,KAAK,MACV,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EACE,WAAW,WAAW,EAAK,WAAW,SAAS,6BAA6B,yBAC5E,CAAA,EACF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;OACE,WAAW,uBAAuB,EAAK,WAAW,SAAS,qCAAqC;iBAE/F,EAAK;OACJ,CAAA,EACJ,kBAAC,KAAD;OAAG,WAAU;iBAAb;QACG,EAAK,cAAc;QAAa;QAAU;QAC1C,EAAK;QAAa;QACjB;SACA,EAAA,CAAA,CACF;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OACE,WAAW,4DAA4D,EAAiB,EAAK,SAAS;iBAErG,EAAK;OACD,CAAA,EACP,kBAAC,QAAD;OACE,WAAW,4DAA4D,EAAe,EAAK,OAAO;iBAEjG,EAAK;OACD,CAAA,CACH;QACF;OA/BC,EAAK,GA+BN,CACN;IACE,CAAA;GACI,CAAA;EAEH,CAAA"}
|
|
1
|
+
{"version":3,"file":"TasksPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/TasksPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport {\n Plus,\n CheckSquare,\n Pencil,\n Trash2,\n ArrowUp,\n ArrowDown,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { getPriorityColor } from \"../../utils/helpers\";\nimport {\n useListTasksQuery,\n useCreateTaskMutation,\n useUpdateTaskMutation,\n useUpdateTaskStatusMutation,\n useDeleteTaskMutation,\n useReorderTasksMutation,\n type TaskFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 25;\n\nconst TASK_STATUSES = [\"todo\", \"in-progress\", \"review\", \"done\"] as const;\nconst TASK_PRIORITIES = [\"low\", \"medium\", \"high\", \"urgent\"] as const;\n\ninterface TaskFormState {\n title: string;\n description: string;\n status: string;\n priority: string;\n assigneeId: string;\n estimatedHours: string;\n dueDate: string;\n}\n\nconst emptyForm: TaskFormState = {\n title: \"\",\n description: \"\",\n status: \"todo\",\n priority: \"medium\",\n assigneeId: \"\",\n estimatedHours: \"\",\n dueDate: \"\",\n};\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\";\nconst labelClass = \"block text-sm font-medium text-text-primary\";\n\nexport function TasksPage() {\n const { projectId } = useParams<{ projectId: string }>();\n\n const [limit, setLimit] = useState(PAGE_SIZE);\n const { data, loading, error, refetch } = useListTasksQuery({\n variables: { projectId: projectId!, limit, offset: 0 },\n skip: !projectId,\n notifyOnNetworkStatusChange: true,\n });\n\n const tasks = data?.tasks?.items ?? [];\n const hasMore = data?.tasks?.hasMore ?? false;\n\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<TaskFieldsFragment | null>(null);\n const [deleteTarget, setDeleteTarget] = useState<TaskFieldsFragment | null>(\n null,\n );\n const [form, setForm] = useState<TaskFormState>(emptyForm);\n\n const [createTask, { loading: creating, error: createError }] =\n useCreateTaskMutation();\n const [updateTask, { loading: updating, error: updateError }] =\n useUpdateTaskMutation();\n const [updateTaskStatus] = useUpdateTaskStatusMutation();\n const [deleteTask, { loading: deleting, error: deleteError }] =\n useDeleteTaskMutation();\n const [reorderTasks, { loading: reordering }] = useReorderTasksMutation();\n\n const openCreate = () => {\n setForm(emptyForm);\n setCreateOpen(true);\n };\n\n const openEdit = (task: TaskFieldsFragment) => {\n setForm({\n title: task.title,\n description: task.description ?? \"\",\n status: task.status,\n priority: task.priority,\n assigneeId: task.assigneeId ?? \"\",\n estimatedHours:\n task.estimatedHours != null ? String(task.estimatedHours) : \"\",\n dueDate: task.dueDate ? task.dueDate.slice(0, 10) : \"\",\n });\n setEditTarget(task);\n };\n\n const handleCreate = async () => {\n if (!projectId || !form.title.trim()) return;\n await createTask({\n variables: {\n input: {\n projectId,\n title: form.title.trim(),\n description: form.description.trim() || undefined,\n status: form.status,\n priority: form.priority,\n assigneeId: form.assigneeId.trim() || undefined,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n dueDate: form.dueDate\n ? new Date(form.dueDate).toISOString()\n : undefined,\n },\n },\n });\n setCreateOpen(false);\n await refetch();\n };\n\n const handleUpdate = async () => {\n if (!editTarget || !form.title.trim()) return;\n await updateTask({\n variables: {\n id: editTarget.id,\n input: {\n title: form.title.trim(),\n description: form.description.trim() || undefined,\n status: form.status,\n priority: form.priority,\n assigneeId: form.assigneeId.trim() || undefined,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n dueDate: form.dueDate\n ? new Date(form.dueDate).toISOString()\n : undefined,\n },\n },\n });\n setEditTarget(null);\n await refetch();\n };\n\n const handleStatusChange = async (\n task: TaskFieldsFragment,\n status: string,\n ) => {\n await updateTaskStatus({ variables: { id: task.id, status } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n await deleteTask({ variables: { id: deleteTarget.id } });\n setDeleteTarget(null);\n await refetch();\n };\n\n const handleMove = async (index: number, direction: -1 | 1) => {\n if (!projectId) return;\n const target = index + direction;\n if (target < 0 || target >= tasks.length) return;\n const ordered = tasks.map((task: TaskFieldsFragment) => task.id);\n const [moved] = ordered.splice(index, 1);\n ordered.splice(target, 0, moved);\n await reorderTasks({ variables: { projectId, taskIds: ordered } });\n await refetch();\n };\n\n const formFields = (\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Title</label>\n <input\n type=\"text\"\n placeholder=\"Task title\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Description</label>\n <textarea\n placeholder=\"Task description\"\n rows={3}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>Status</label>\n <select\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n className={inputClass}\n >\n {TASK_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className={labelClass}>Priority</label>\n <select\n value={form.priority}\n onChange={(e) => setForm({ ...form, priority: e.target.value })}\n className={inputClass}\n >\n {TASK_PRIORITIES.map((p) => (\n <option key={p} value={p}>\n {p}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label className={labelClass}>Assignee ID</label>\n <input\n type=\"text\"\n placeholder=\"User ID\"\n value={form.assigneeId}\n onChange={(e) => setForm({ ...form, assigneeId: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Estimated Hours</label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={form.estimatedHours}\n onChange={(e) =>\n setForm({ ...form, estimatedHours: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Due Date</label>\n <input\n type=\"date\"\n value={form.dueDate}\n onChange={(e) => setForm({ ...form, dueDate: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n );\n\n if (error) {\n return (\n <PageLayout title=\"Tasks\" description={`Tasks for project ${projectId}`}>\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n Failed to load tasks. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Tasks\"\n description={`Tasks for project ${projectId}`}\n purpose=\"Break a project down into the concrete pieces of work that need doing, then track hours against each one — so you can see what's done, what's blocking, and where effort is actually being spent.\"\n actions={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add Task\n </button>\n }\n >\n {loading && tasks.length === 0 ? (\n <LoadingSkeleton variant=\"list\" count={5} />\n ) : tasks.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No tasks yet\"\n description=\"Add a task to break this project into trackable work.\"\n action={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add Task\n </button>\n }\n />\n ) : (\n <>\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"divide-y divide-border\">\n {tasks.map((task: TaskFieldsFragment, index: number) => (\n <div\n key={task.id}\n className=\"flex flex-col gap-3 p-4 transition-colors hover:bg-bg-sunken/50 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex items-center gap-3\">\n <CheckSquare\n className={`h-5 w-5 shrink-0 ${task.status === \"done\" ? \"text-status-success-text\" : \"text-text-secondary\"}`}\n />\n <div>\n <p\n className={`text-sm font-medium ${task.status === \"done\" ? \"text-text-secondary line-through\" : \"text-text-primary\"}`}\n >\n {task.title}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {task.assigneeId ?? \"Unassigned\"} ·{\" \"}\n {task.trackedHours}h tracked\n </p>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getPriorityColor(task.priority)}`}\n >\n {task.priority}\n </span>\n <select\n value={task.status}\n onChange={(e) =>\n void handleStatusChange(task, e.target.value)\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs font-medium text-text-primary\"\n aria-label=\"Task status\"\n >\n {TASK_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n <button\n onClick={() => void handleMove(index, -1)}\n disabled={index === 0 || reordering}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-40\"\n aria-label=\"Move up\"\n >\n <ArrowUp className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => void handleMove(index, 1)}\n disabled={index === tasks.length - 1 || reordering}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-40\"\n aria-label=\"Move down\"\n >\n <ArrowDown className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => openEdit(task)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit task\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeleteTarget(task)}\n className=\"rounded-md p-1 text-status-error-text hover:bg-status-error-bg\"\n aria-label=\"Delete task\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ))}\n </div>\n </GlassCard>\n {hasMore && (\n <div className=\"mt-6 flex justify-center\">\n <button\n onClick={() => setLimit((prev) => prev + PAGE_SIZE)}\n disabled={loading}\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 {loading ? \"Loading…\" : \"Load more\"}\n </button>\n </div>\n )}\n </>\n )}\n\n {/* Create modal */}\n <Modal\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title=\"Add Task\"\n description=\"Break this project into a trackable piece of work.\"\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setCreateOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleCreate}\n disabled={creating || !form.title.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 {creating ? \"Creating…\" : \"Create Task\"}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to create task. Please try again.\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Edit modal */}\n <Modal\n open={editTarget !== null}\n onClose={() => setEditTarget(null)}\n title=\"Edit Task\"\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setEditTarget(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleUpdate}\n disabled={updating || !form.title.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 {updating ? \"Saving…\" : \"Save Changes\"}\n </button>\n </>\n }\n >\n {updateError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to update task. Please try again.\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title=\"Delete Task\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteTarget(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deleting ? \"Deleting…\" : \"Delete\"}\n </button>\n </>\n }\n >\n {deleteError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to delete task. Please try again.\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-semibold text-text-primary\">\n {deleteTarget?.title}\n </span>\n ? This action cannot be undone.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|