@burdenoff/microfe-timecampus 2026.703.1 → 2026.703.3
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-types.js +28 -0
- package/dist/generated/wspace-types.js.map +1 -0
- package/dist/index.js +4 -4
- 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 +432 -118
- 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 +562 -85
- 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 +208 -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 +339 -110
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- 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/package.json +1 -1
|
@@ -2,38 +2,99 @@ import { useTimeCampusNavigate as e } from "../../providers/TimeCampusProvider.j
|
|
|
2
2
|
import { formatCurrency as t, formatHours as n } from "../../utils/helpers.js";
|
|
3
3
|
import { StatCard as r } from "../../components/StatCard.js";
|
|
4
4
|
import { LoadingSkeleton as i } from "../../components/LoadingSkeleton.js";
|
|
5
|
-
import {
|
|
6
|
-
import { PageLayout as
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
5
|
+
import { useArchiveProjectMutation as a, useDeleteProjectMutation as o, useGetProjectQuery as s, useGetProjectStatsQuery as c, useUpdateProjectMutation as l } from "../../../generated/wspace-operations.js";
|
|
6
|
+
import { PageLayout as u } from "../../components/PageLayout.js";
|
|
7
|
+
import { Modal as d } from "../../components/Modal.js";
|
|
8
|
+
import { useState as f } from "react";
|
|
9
|
+
import { Archive as p, Clock as m, FolderOpen as h, Pencil as g, Trash2 as _, TrendingUp as v, Users as y } from "lucide-react";
|
|
10
|
+
import { useParams as b } from "react-router-dom";
|
|
11
|
+
import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
|
|
12
|
+
import { NextSteps as w } from "@burdenoff/fe-libs/ui";
|
|
11
13
|
//#region src/timecampus/pages/projects/ProjectDetailPage.tsx
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}),
|
|
20
|
-
|
|
14
|
+
var T = [
|
|
15
|
+
"active",
|
|
16
|
+
"on-hold",
|
|
17
|
+
"completed",
|
|
18
|
+
"archived"
|
|
19
|
+
], E = "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", D = "block text-sm font-medium text-text-primary";
|
|
20
|
+
function O() {
|
|
21
|
+
let { projectId: O } = b(), k = e(), { data: A, loading: j, error: M, refetch: N } = s({
|
|
22
|
+
variables: { id: O },
|
|
23
|
+
skip: !O
|
|
24
|
+
}), { data: P, loading: F } = c({
|
|
25
|
+
variables: { id: O },
|
|
26
|
+
skip: !O
|
|
27
|
+
}), I = A?.project, L = P?.projectStats, [R, z] = f(!1), [B, V] = f(!1), [H, U] = f({
|
|
28
|
+
name: "",
|
|
29
|
+
clientId: "",
|
|
30
|
+
description: "",
|
|
31
|
+
status: "active",
|
|
32
|
+
estimatedHours: "",
|
|
33
|
+
hourlyRate: ""
|
|
34
|
+
}), [W, { loading: G, error: K }] = l(), [q, { loading: J }] = a(), [Y, { loading: X, error: Z }] = o(), Q = () => {
|
|
35
|
+
I && (U({
|
|
36
|
+
name: I.name,
|
|
37
|
+
clientId: I.clientId ?? "",
|
|
38
|
+
description: I.description ?? "",
|
|
39
|
+
status: I.status,
|
|
40
|
+
estimatedHours: I.estimatedHours == null ? "" : String(I.estimatedHours),
|
|
41
|
+
hourlyRate: I.hourlyRate == null ? "" : String(I.hourlyRate)
|
|
42
|
+
}), z(!0));
|
|
43
|
+
}, $ = async () => {
|
|
44
|
+
!O || !H.name.trim() || (await W({ variables: {
|
|
45
|
+
id: O,
|
|
46
|
+
input: {
|
|
47
|
+
name: H.name.trim(),
|
|
48
|
+
description: H.description.trim() || void 0,
|
|
49
|
+
clientId: H.clientId.trim() || void 0,
|
|
50
|
+
status: H.status,
|
|
51
|
+
estimatedHours: H.estimatedHours ? Number(H.estimatedHours) : void 0,
|
|
52
|
+
hourlyRate: H.hourlyRate ? Number(H.hourlyRate) : void 0
|
|
53
|
+
}
|
|
54
|
+
} }), z(!1), await N());
|
|
55
|
+
}, ee = async () => {
|
|
56
|
+
O && (await q({ variables: { id: O } }), await N());
|
|
57
|
+
};
|
|
58
|
+
return M ? /* @__PURE__ */ S(u, {
|
|
21
59
|
title: "Project Details",
|
|
22
|
-
description: `Project ID: ${
|
|
23
|
-
children: /* @__PURE__ */
|
|
60
|
+
description: `Project ID: ${O}`,
|
|
61
|
+
children: /* @__PURE__ */ S("div", {
|
|
24
62
|
className: "rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text",
|
|
25
63
|
children: "Failed to load project details. Please try again later."
|
|
26
64
|
})
|
|
27
|
-
}) :
|
|
65
|
+
}) : j || F ? /* @__PURE__ */ S(u, {
|
|
28
66
|
title: "Project Details",
|
|
29
|
-
description: `Project ID: ${
|
|
30
|
-
children: /* @__PURE__ */
|
|
31
|
-
}) : /* @__PURE__ */
|
|
32
|
-
title:
|
|
33
|
-
description:
|
|
67
|
+
description: `Project ID: ${O}`,
|
|
68
|
+
children: /* @__PURE__ */ S(i, { variant: "detail" })
|
|
69
|
+
}) : /* @__PURE__ */ C(u, {
|
|
70
|
+
title: I?.name ?? "Project Details",
|
|
71
|
+
description: I?.description ?? `Project ID: ${O}`,
|
|
34
72
|
purpose: "See everything happening on this project in one place — logged hours, task progress and budget burn — so you know whether it's on track and where the time is going before it overruns.",
|
|
73
|
+
actions: /* @__PURE__ */ C("div", {
|
|
74
|
+
className: "flex items-center gap-2",
|
|
75
|
+
children: [
|
|
76
|
+
/* @__PURE__ */ C("button", {
|
|
77
|
+
onClick: Q,
|
|
78
|
+
disabled: !I,
|
|
79
|
+
className: "inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50",
|
|
80
|
+
children: [/* @__PURE__ */ S(g, { className: "h-4 w-4" }), "Edit"]
|
|
81
|
+
}),
|
|
82
|
+
I?.status !== "archived" && /* @__PURE__ */ C("button", {
|
|
83
|
+
onClick: () => void ee(),
|
|
84
|
+
disabled: !I || J,
|
|
85
|
+
className: "inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50",
|
|
86
|
+
children: [/* @__PURE__ */ S(p, { className: "h-4 w-4" }), J ? "Archiving…" : "Archive"]
|
|
87
|
+
}),
|
|
88
|
+
/* @__PURE__ */ C("button", {
|
|
89
|
+
onClick: () => V(!0),
|
|
90
|
+
disabled: !I,
|
|
91
|
+
className: "inline-flex items-center gap-1.5 rounded-md border border-status-error-bg/40 px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg disabled:opacity-50",
|
|
92
|
+
children: [/* @__PURE__ */ S(_, { className: "h-4 w-4" }), "Delete"]
|
|
93
|
+
})
|
|
94
|
+
]
|
|
95
|
+
}),
|
|
35
96
|
children: [
|
|
36
|
-
/* @__PURE__ */
|
|
97
|
+
/* @__PURE__ */ S(w, {
|
|
37
98
|
storageKey: "timecampus-project-detail",
|
|
38
99
|
className: "mb-6",
|
|
39
100
|
steps: [
|
|
@@ -41,82 +102,218 @@ function g() {
|
|
|
41
102
|
id: "log-time",
|
|
42
103
|
label: "Log time to this project",
|
|
43
104
|
description: "Start the timer or add a manual entry for this work.",
|
|
44
|
-
onClick: () =>
|
|
105
|
+
onClick: () => k("/time/timer")
|
|
45
106
|
},
|
|
46
107
|
{
|
|
47
108
|
id: "view-tasks",
|
|
48
109
|
label: "Review tasks",
|
|
49
110
|
description: "Break the project into trackable tasks.",
|
|
50
|
-
onClick: () =>
|
|
111
|
+
onClick: () => k(`/projects/${O ?? ""}/tasks`)
|
|
51
112
|
},
|
|
52
113
|
{
|
|
53
114
|
id: "open-reports",
|
|
54
115
|
label: "Open reports",
|
|
55
116
|
description: "Analyse this project's time and budget over time.",
|
|
56
|
-
onClick: () =>
|
|
117
|
+
onClick: () => k("/reports")
|
|
57
118
|
}
|
|
58
119
|
]
|
|
59
120
|
}),
|
|
60
|
-
/* @__PURE__ */
|
|
121
|
+
/* @__PURE__ */ C("div", {
|
|
61
122
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
62
123
|
children: [
|
|
63
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ S(r, {
|
|
64
125
|
featured: !0,
|
|
65
126
|
title: "Tracked Hours",
|
|
66
|
-
value: n(
|
|
67
|
-
icon: /* @__PURE__ */
|
|
127
|
+
value: n(L?.totalHours ?? I?.trackedHours ?? 0),
|
|
128
|
+
icon: /* @__PURE__ */ S(m, { className: "h-5 w-5" })
|
|
68
129
|
}),
|
|
69
|
-
/* @__PURE__ */
|
|
130
|
+
/* @__PURE__ */ S(r, {
|
|
70
131
|
title: "Tasks",
|
|
71
|
-
value: `${
|
|
72
|
-
icon: /* @__PURE__ */
|
|
132
|
+
value: `${L?.completedTasks ?? 0} / ${L?.totalTasks ?? 0}`,
|
|
133
|
+
icon: /* @__PURE__ */ S(y, { className: "h-5 w-5" }),
|
|
73
134
|
description: "completed / total"
|
|
74
135
|
}),
|
|
75
|
-
/* @__PURE__ */
|
|
136
|
+
/* @__PURE__ */ S(r, {
|
|
76
137
|
title: "Progress",
|
|
77
|
-
value:
|
|
78
|
-
icon: /* @__PURE__ */
|
|
138
|
+
value: L?.totalTasks ? `${Math.round((L?.completedTasks ?? 0) / L.totalTasks * 100)}%` : "0%",
|
|
139
|
+
icon: /* @__PURE__ */ S(v, { className: "h-5 w-5" })
|
|
79
140
|
}),
|
|
80
|
-
/* @__PURE__ */
|
|
141
|
+
/* @__PURE__ */ S(r, {
|
|
81
142
|
title: "Budget Used",
|
|
82
|
-
value: t(
|
|
83
|
-
icon: /* @__PURE__ */
|
|
84
|
-
description:
|
|
143
|
+
value: t(L?.budgetUsed ?? 0, I?.currency ?? "USD"),
|
|
144
|
+
icon: /* @__PURE__ */ S(h, { className: "h-5 w-5" }),
|
|
145
|
+
description: L?.budgetRemaining == null ? void 0 : `${t(L.budgetRemaining, I?.currency ?? "USD")} remaining`
|
|
85
146
|
})
|
|
86
147
|
]
|
|
87
148
|
}),
|
|
88
|
-
/* @__PURE__ */
|
|
149
|
+
/* @__PURE__ */ C("div", {
|
|
89
150
|
className: "grid grid-cols-1 gap-6 lg:grid-cols-2",
|
|
90
|
-
children: [/* @__PURE__ */
|
|
151
|
+
children: [/* @__PURE__ */ C("div", {
|
|
91
152
|
className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6",
|
|
92
|
-
children: [/* @__PURE__ */
|
|
153
|
+
children: [/* @__PURE__ */ S("h3", {
|
|
93
154
|
className: "mb-4 text-sm font-semibold text-text-primary",
|
|
94
155
|
children: "Recent Activity"
|
|
95
|
-
}), /* @__PURE__ */
|
|
156
|
+
}), /* @__PURE__ */ S("div", {
|
|
96
157
|
className: "flex h-48 items-center justify-center rounded-lg border border-dashed border-border-subtle",
|
|
97
|
-
children: /* @__PURE__ */
|
|
158
|
+
children: /* @__PURE__ */ S("p", {
|
|
98
159
|
className: "text-sm text-text-secondary",
|
|
99
160
|
children: "Activity timeline will render here"
|
|
100
161
|
})
|
|
101
162
|
})]
|
|
102
|
-
}), /* @__PURE__ */
|
|
163
|
+
}), /* @__PURE__ */ C("div", {
|
|
103
164
|
className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6",
|
|
104
|
-
children: [/* @__PURE__ */
|
|
165
|
+
children: [/* @__PURE__ */ S("h3", {
|
|
105
166
|
className: "mb-4 text-sm font-semibold text-text-primary",
|
|
106
167
|
children: "Time Distribution"
|
|
107
|
-
}), /* @__PURE__ */
|
|
168
|
+
}), /* @__PURE__ */ S("div", {
|
|
108
169
|
className: "flex h-48 items-center justify-center rounded-lg border border-dashed border-border-subtle",
|
|
109
|
-
children: /* @__PURE__ */
|
|
170
|
+
children: /* @__PURE__ */ S("p", {
|
|
110
171
|
className: "text-sm text-text-secondary",
|
|
111
172
|
children: "Chart will render here"
|
|
112
173
|
})
|
|
113
174
|
})]
|
|
114
175
|
})]
|
|
176
|
+
}),
|
|
177
|
+
/* @__PURE__ */ C(d, {
|
|
178
|
+
open: R,
|
|
179
|
+
onClose: () => z(!1),
|
|
180
|
+
title: "Edit Project",
|
|
181
|
+
size: "xl",
|
|
182
|
+
footer: /* @__PURE__ */ C(x, { children: [/* @__PURE__ */ S("button", {
|
|
183
|
+
onClick: () => z(!1),
|
|
184
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
185
|
+
children: "Cancel"
|
|
186
|
+
}), /* @__PURE__ */ S("button", {
|
|
187
|
+
onClick: $,
|
|
188
|
+
disabled: G || !H.name.trim(),
|
|
189
|
+
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",
|
|
190
|
+
children: G ? "Saving…" : "Save Changes"
|
|
191
|
+
})] }),
|
|
192
|
+
children: [K && /* @__PURE__ */ S("div", {
|
|
193
|
+
className: "mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
|
|
194
|
+
children: "Failed to update project. Please try again."
|
|
195
|
+
}), /* @__PURE__ */ C("div", {
|
|
196
|
+
className: "space-y-4",
|
|
197
|
+
children: [
|
|
198
|
+
/* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("label", {
|
|
199
|
+
className: D,
|
|
200
|
+
children: "Project Name"
|
|
201
|
+
}), /* @__PURE__ */ S("input", {
|
|
202
|
+
type: "text",
|
|
203
|
+
value: H.name,
|
|
204
|
+
onChange: (e) => U({
|
|
205
|
+
...H,
|
|
206
|
+
name: e.target.value
|
|
207
|
+
}),
|
|
208
|
+
className: E
|
|
209
|
+
})] }),
|
|
210
|
+
/* @__PURE__ */ C("div", {
|
|
211
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
212
|
+
children: [/* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("label", {
|
|
213
|
+
className: D,
|
|
214
|
+
children: "Client"
|
|
215
|
+
}), /* @__PURE__ */ S("input", {
|
|
216
|
+
type: "text",
|
|
217
|
+
value: H.clientId,
|
|
218
|
+
onChange: (e) => U({
|
|
219
|
+
...H,
|
|
220
|
+
clientId: e.target.value
|
|
221
|
+
}),
|
|
222
|
+
className: E
|
|
223
|
+
})] }), /* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("label", {
|
|
224
|
+
className: D,
|
|
225
|
+
children: "Status"
|
|
226
|
+
}), /* @__PURE__ */ S("select", {
|
|
227
|
+
value: H.status,
|
|
228
|
+
onChange: (e) => U({
|
|
229
|
+
...H,
|
|
230
|
+
status: e.target.value
|
|
231
|
+
}),
|
|
232
|
+
className: E,
|
|
233
|
+
children: T.map((e) => /* @__PURE__ */ S("option", {
|
|
234
|
+
value: e,
|
|
235
|
+
children: e
|
|
236
|
+
}, e))
|
|
237
|
+
})] })]
|
|
238
|
+
}),
|
|
239
|
+
/* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("label", {
|
|
240
|
+
className: D,
|
|
241
|
+
children: "Description"
|
|
242
|
+
}), /* @__PURE__ */ S("textarea", {
|
|
243
|
+
rows: 3,
|
|
244
|
+
value: H.description,
|
|
245
|
+
onChange: (e) => U({
|
|
246
|
+
...H,
|
|
247
|
+
description: e.target.value
|
|
248
|
+
}),
|
|
249
|
+
className: E
|
|
250
|
+
})] }),
|
|
251
|
+
/* @__PURE__ */ C("div", {
|
|
252
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
253
|
+
children: [/* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("label", {
|
|
254
|
+
className: D,
|
|
255
|
+
children: "Estimated Hours"
|
|
256
|
+
}), /* @__PURE__ */ S("input", {
|
|
257
|
+
type: "number",
|
|
258
|
+
value: H.estimatedHours,
|
|
259
|
+
onChange: (e) => U({
|
|
260
|
+
...H,
|
|
261
|
+
estimatedHours: e.target.value
|
|
262
|
+
}),
|
|
263
|
+
className: E
|
|
264
|
+
})] }), /* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("label", {
|
|
265
|
+
className: D,
|
|
266
|
+
children: "Hourly Rate"
|
|
267
|
+
}), /* @__PURE__ */ S("input", {
|
|
268
|
+
type: "number",
|
|
269
|
+
value: H.hourlyRate,
|
|
270
|
+
onChange: (e) => U({
|
|
271
|
+
...H,
|
|
272
|
+
hourlyRate: e.target.value
|
|
273
|
+
}),
|
|
274
|
+
className: E
|
|
275
|
+
})] })]
|
|
276
|
+
})
|
|
277
|
+
]
|
|
278
|
+
})]
|
|
279
|
+
}),
|
|
280
|
+
/* @__PURE__ */ C(d, {
|
|
281
|
+
open: B,
|
|
282
|
+
onClose: () => V(!1),
|
|
283
|
+
title: "Delete Project",
|
|
284
|
+
size: "sm",
|
|
285
|
+
footer: /* @__PURE__ */ C(x, { children: [/* @__PURE__ */ S("button", {
|
|
286
|
+
onClick: () => V(!1),
|
|
287
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
288
|
+
children: "Cancel"
|
|
289
|
+
}), /* @__PURE__ */ S("button", {
|
|
290
|
+
onClick: async () => {
|
|
291
|
+
O && (await Y({ variables: { id: O } }), V(!1), k("/projects"));
|
|
292
|
+
},
|
|
293
|
+
disabled: X,
|
|
294
|
+
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",
|
|
295
|
+
children: X ? "Deleting…" : "Delete"
|
|
296
|
+
})] }),
|
|
297
|
+
children: [Z && /* @__PURE__ */ S("div", {
|
|
298
|
+
className: "mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
|
|
299
|
+
children: "Failed to delete project. Please try again."
|
|
300
|
+
}), /* @__PURE__ */ C("p", {
|
|
301
|
+
className: "text-sm text-text-secondary",
|
|
302
|
+
children: [
|
|
303
|
+
"Are you sure you want to delete",
|
|
304
|
+
" ",
|
|
305
|
+
/* @__PURE__ */ S("span", {
|
|
306
|
+
className: "font-semibold text-text-primary",
|
|
307
|
+
children: I?.name
|
|
308
|
+
}),
|
|
309
|
+
"? This action cannot be undone."
|
|
310
|
+
]
|
|
311
|
+
})]
|
|
115
312
|
})
|
|
116
313
|
]
|
|
117
314
|
});
|
|
118
315
|
}
|
|
119
316
|
//#endregion
|
|
120
|
-
export {
|
|
317
|
+
export { O as ProjectDetailPage };
|
|
121
318
|
|
|
122
319
|
//# sourceMappingURL=ProjectDetailPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectDetailPage.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { FolderOpen, Clock, Users, TrendingUp } from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { formatHours, formatCurrency } from \"../../utils/helpers\";\nimport {\n useGetProjectQuery,\n useGetProjectStatsQuery,\n} from \"@/generated/wspace-operations\";\n\nexport function ProjectDetailPage() {\n const { projectId } = useParams<{ projectId: string }>();\n const navigate = useTimeCampusNavigate();\n\n const {\n data: projectData,\n loading: projectLoading,\n error: projectError,\n } = useGetProjectQuery({\n variables: { id: projectId! },\n skip: !projectId,\n });\n\n const { data: statsData, loading: statsLoading } = useGetProjectStatsQuery({\n variables: { id: projectId! },\n skip: !projectId,\n });\n\n const project = projectData?.project;\n const stats = statsData?.projectStats;\n\n if (projectError) {\n return (\n <PageLayout\n title=\"Project Details\"\n description={`Project ID: ${projectId}`}\n >\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n Failed to load project details. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n if (projectLoading || statsLoading) {\n return (\n <PageLayout\n title=\"Project Details\"\n description={`Project ID: ${projectId}`}\n >\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={project?.name ?? \"Project Details\"}\n description={project?.description ?? `Project ID: ${projectId}`}\n purpose=\"See everything happening on this project in one place — logged hours, task progress and budget burn — so you know whether it's on track and where the time is going before it overruns.\"\n >\n <NextSteps\n storageKey=\"timecampus-project-detail\"\n className=\"mb-6\"\n steps={[\n {\n id: \"log-time\",\n label: \"Log time to this project\",\n description: \"Start the timer or add a manual entry for this work.\",\n onClick: () => navigate(\"/time/timer\"),\n },\n {\n id: \"view-tasks\",\n label: \"Review tasks\",\n description: \"Break the project into trackable tasks.\",\n onClick: () => navigate(`/projects/${projectId ?? \"\"}/tasks`),\n },\n {\n id: \"open-reports\",\n label: \"Open reports\",\n description: \"Analyse this project's time and budget over time.\",\n onClick: () => navigate(\"/reports\"),\n },\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=\"Tracked Hours\"\n value={formatHours(stats?.totalHours ?? project?.trackedHours ?? 0)}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Tasks\"\n value={`${stats?.completedTasks ?? 0} / ${stats?.totalTasks ?? 0}`}\n icon={<Users className=\"h-5 w-5\" />}\n description=\"completed / total\"\n />\n <StatCard\n title=\"Progress\"\n value={\n stats?.totalTasks\n ? `${Math.round(((stats?.completedTasks ?? 0) / stats.totalTasks) * 100)}%`\n : \"0%\"\n }\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Budget Used\"\n value={formatCurrency(\n stats?.budgetUsed ?? 0,\n project?.currency ?? \"USD\",\n )}\n icon={<FolderOpen className=\"h-5 w-5\" />}\n description={\n stats?.budgetRemaining != null\n ? `${formatCurrency(stats.budgetRemaining, project?.currency ?? \"USD\")} remaining`\n : undefined\n }\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\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 Recent Activity\n </h3>\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 Activity timeline will render here\n </p>\n </div>\n </div>\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 Time Distribution\n </h3>\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 Chart will render here\n </p>\n </div>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;AAaA,SAAgB,IAAoB;CAClC,IAAM,EAAE,iBAAc,GAAkC,EAClD,IAAW,GAAuB,EAElC,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,EAAmB;EACrB,WAAW,EAAE,IAAI,GAAY;EAC7B,MAAM,CAAC;EACR,CAAC,EAEI,EAAE,MAAM,GAAW,SAAS,MAAiB,EAAwB;EACzE,WAAW,EAAE,IAAI,GAAY;EAC7B,MAAM,CAAC;EACR,CAAC,EAEI,IAAU,GAAa,SACvB,IAAQ,GAAW;AA0BzB,QAxBI,IAEA,kBAAC,GAAD;EACE,OAAM;EACN,aAAa,eAAe;YAE5B,kBAAC,OAAD;GAAK,WAAU;aAAoG;GAE7G,CAAA;EACK,CAAA,GAIb,KAAkB,IAElB,kBAAC,GAAD;EACE,OAAM;EACN,aAAa,eAAe;YAE5B,kBAAC,GAAD,EAAiB,SAAQ,UAAW,CAAA;EACzB,CAAA,GAKf,kBAAC,GAAD;EACE,OAAO,GAAS,QAAQ;EACxB,aAAa,GAAS,eAAe,eAAe;EACpD,SAAQ;YAHV;GAKE,kBAAC,GAAD;IACE,YAAW;IACX,WAAU;IACV,OAAO;KACL;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,eAAe,EAAS,cAAc;MACvC;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,eAAe,EAAS,aAAa,KAAa,GAAG,QAAQ;MAC9D;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,eAAe,EAAS,WAAW;MACpC;KACF;IACD,CAAA;GACF,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,UAAA;MACA,OAAM;MACN,OAAO,EAAY,GAAO,cAAc,GAAS,gBAAgB,EAAE;MACnE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;MACnC,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,GAAG,GAAO,kBAAkB,EAAE,KAAK,GAAO,cAAc;MAC/D,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;MACnC,aAAY;MACZ,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OACE,GAAO,aACH,GAAG,KAAK,OAAQ,GAAO,kBAAkB,KAAK,EAAM,aAAc,IAAI,CAAC,KACvE;MAEN,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;MACxC,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EACL,GAAO,cAAc,GACrB,GAAS,YAAY,MACtB;MACD,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;MACxC,aACE,GAAO,mBAAmB,OAEtB,KAAA,IADA,GAAG,EAAe,EAAM,iBAAiB,GAAS,YAAY,MAAM,CAAC;MAG3E,CAAA;KACE;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA+C;MAExD,CAAA,EACL,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAEvC,CAAA;MACA,CAAA,CACF;QACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA+C;MAExD,CAAA,EACL,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAEvC,CAAA;MACA,CAAA,CACF;OACF;;GACK"}
|
|
1
|
+
{"version":3,"file":"ProjectDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport {\n FolderOpen,\n Clock,\n Users,\n TrendingUp,\n Pencil,\n Archive,\n Trash2,\n} from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { formatHours, formatCurrency } from \"../../utils/helpers\";\nimport {\n useGetProjectQuery,\n useGetProjectStatsQuery,\n useUpdateProjectMutation,\n useArchiveProjectMutation,\n useDeleteProjectMutation,\n} from \"@/generated/wspace-operations\";\n\nconst PROJECT_STATUSES = [\n \"active\",\n \"on-hold\",\n \"completed\",\n \"archived\",\n] as const;\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 ProjectDetailPage() {\n const { projectId } = useParams<{ projectId: string }>();\n const navigate = useTimeCampusNavigate();\n\n const {\n data: projectData,\n loading: projectLoading,\n error: projectError,\n refetch,\n } = useGetProjectQuery({\n variables: { id: projectId! },\n skip: !projectId,\n });\n\n const { data: statsData, loading: statsLoading } = useGetProjectStatsQuery({\n variables: { id: projectId! },\n skip: !projectId,\n });\n\n const project = projectData?.project;\n const stats = statsData?.projectStats;\n\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n const [form, setForm] = useState({\n name: \"\",\n clientId: \"\",\n description: \"\",\n status: \"active\",\n estimatedHours: \"\",\n hourlyRate: \"\",\n });\n\n const [updateProject, { loading: updating, error: updateError }] =\n useUpdateProjectMutation();\n const [archiveProject, { loading: archiving }] = useArchiveProjectMutation();\n const [deleteProject, { loading: deleting, error: deleteError }] =\n useDeleteProjectMutation();\n\n const openEdit = () => {\n if (!project) return;\n setForm({\n name: project.name,\n clientId: project.clientId ?? \"\",\n description: project.description ?? \"\",\n status: project.status,\n estimatedHours:\n project.estimatedHours != null ? String(project.estimatedHours) : \"\",\n hourlyRate: project.hourlyRate != null ? String(project.hourlyRate) : \"\",\n });\n setEditOpen(true);\n };\n\n const handleUpdate = async () => {\n if (!projectId || !form.name.trim()) return;\n await updateProject({\n variables: {\n id: projectId,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setEditOpen(false);\n await refetch();\n };\n\n const handleArchive = async () => {\n if (!projectId) return;\n await archiveProject({ variables: { id: projectId } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!projectId) return;\n await deleteProject({ variables: { id: projectId } });\n setDeleteOpen(false);\n navigate(\"/projects\");\n };\n\n if (projectError) {\n return (\n <PageLayout\n title=\"Project Details\"\n description={`Project ID: ${projectId}`}\n >\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n Failed to load project details. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n if (projectLoading || statsLoading) {\n return (\n <PageLayout\n title=\"Project Details\"\n description={`Project ID: ${projectId}`}\n >\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={project?.name ?? \"Project Details\"}\n description={project?.description ?? `Project ID: ${projectId}`}\n purpose=\"See everything happening on this project in one place — logged hours, task progress and budget burn — so you know whether it's on track and where the time is going before it overruns.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n onClick={openEdit}\n disabled={!project}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n <Pencil className=\"h-4 w-4\" />\n Edit\n </button>\n {project?.status !== \"archived\" && (\n <button\n onClick={() => void handleArchive()}\n disabled={!project || archiving}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n <Archive className=\"h-4 w-4\" />\n {archiving ? \"Archiving…\" : \"Archive\"}\n </button>\n )}\n <button\n onClick={() => setDeleteOpen(true)}\n disabled={!project}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-status-error-bg/40 px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Delete\n </button>\n </div>\n }\n >\n <NextSteps\n storageKey=\"timecampus-project-detail\"\n className=\"mb-6\"\n steps={[\n {\n id: \"log-time\",\n label: \"Log time to this project\",\n description: \"Start the timer or add a manual entry for this work.\",\n onClick: () => navigate(\"/time/timer\"),\n },\n {\n id: \"view-tasks\",\n label: \"Review tasks\",\n description: \"Break the project into trackable tasks.\",\n onClick: () => navigate(`/projects/${projectId ?? \"\"}/tasks`),\n },\n {\n id: \"open-reports\",\n label: \"Open reports\",\n description: \"Analyse this project's time and budget over time.\",\n onClick: () => navigate(\"/reports\"),\n },\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=\"Tracked Hours\"\n value={formatHours(stats?.totalHours ?? project?.trackedHours ?? 0)}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Tasks\"\n value={`${stats?.completedTasks ?? 0} / ${stats?.totalTasks ?? 0}`}\n icon={<Users className=\"h-5 w-5\" />}\n description=\"completed / total\"\n />\n <StatCard\n title=\"Progress\"\n value={\n stats?.totalTasks\n ? `${Math.round(((stats?.completedTasks ?? 0) / stats.totalTasks) * 100)}%`\n : \"0%\"\n }\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Budget Used\"\n value={formatCurrency(\n stats?.budgetUsed ?? 0,\n project?.currency ?? \"USD\",\n )}\n icon={<FolderOpen className=\"h-5 w-5\" />}\n description={\n stats?.budgetRemaining != null\n ? `${formatCurrency(stats.budgetRemaining, project?.currency ?? \"USD\")} remaining`\n : undefined\n }\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\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 Recent Activity\n </h3>\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 Activity timeline will render here\n </p>\n </div>\n </div>\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 Time Distribution\n </h3>\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 Chart will render here\n </p>\n </div>\n </div>\n </div>\n\n {/* Edit modal */}\n <Modal\n open={editOpen}\n onClose={() => setEditOpen(false)}\n title=\"Edit Project\"\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setEditOpen(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={handleUpdate}\n disabled={updating || !form.name.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 project. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Project Name</label>\n <input\n type=\"text\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: 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}>Client</label>\n <input\n type=\"text\"\n value={form.clientId}\n onChange={(e) => setForm({ ...form, clientId: e.target.value })}\n className={inputClass}\n />\n </div>\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 {PROJECT_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className={labelClass}>Description</label>\n <textarea\n rows={3}\n value={form.description}\n onChange={(e) =>\n setForm({ ...form, description: e.target.value })\n }\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}>Estimated Hours</label>\n <input\n type=\"number\"\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}>Hourly Rate</label>\n <input\n type=\"number\"\n value={form.hourlyRate}\n onChange={(e) =>\n setForm({ ...form, hourlyRate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n </div>\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteOpen}\n onClose={() => setDeleteOpen(false)}\n title=\"Delete Project\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteOpen(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={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 project. 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 {project?.name}\n </span>\n ? This action cannot be undone.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|