@burdenoff/microfe-proserviceworld 2026.706.2 → 2026.706.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/proserviceworld/components/DiscussionThread.js +1 -1
- package/dist/proserviceworld/components/DiscussionThread.js.map +1 -1
- package/dist/proserviceworld/components/ui.js +18 -18
- package/dist/proserviceworld/components/ui.js.map +1 -1
- package/dist/proserviceworld/hooks/useEntityComments.js.map +1 -1
- package/dist/proserviceworld/pages/AnalyticsPage.js +277 -111
- package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ClientDetailPage.js +219 -151
- package/dist/proserviceworld/pages/ClientDetailPage.js.map +1 -1
- package/dist/proserviceworld/pages/ClientsPage.js +148 -127
- package/dist/proserviceworld/pages/ClientsPage.js.map +1 -1
- package/dist/proserviceworld/pages/CompliancePage.js +140 -96
- package/dist/proserviceworld/pages/CompliancePage.js.map +1 -1
- package/dist/proserviceworld/pages/DashboardPage.js +221 -104
- package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
- package/dist/proserviceworld/pages/EngagementDetailPage.js +211 -181
- package/dist/proserviceworld/pages/EngagementDetailPage.js.map +1 -1
- package/dist/proserviceworld/pages/EngagementsPage.js +144 -106
- package/dist/proserviceworld/pages/EngagementsPage.js.map +1 -1
- package/dist/proserviceworld/pages/InvoicesPage.js +144 -129
- package/dist/proserviceworld/pages/InvoicesPage.js.map +1 -1
- package/dist/proserviceworld/pages/MattersPage.js +163 -122
- package/dist/proserviceworld/pages/MattersPage.js.map +1 -1
- package/dist/proserviceworld/pages/TimePage.js +112 -92
- package/dist/proserviceworld/pages/TimePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import { useApproveTimeEntry as e, useEngagements as t, useLogTimeEntry as n, useMatters as r, useSubmitTimeEntry as i, useTimeEntries as a } from "../hooks/useProServiceWorldApi.js";
|
|
2
2
|
import { Banner as o, Empty as s, ErrorState as c, Field as l, LoadingBlock as u, MetricCard as d, Modal as f, NativeSelect as p, PageHeader as m, SectionCard as h, StatusPill as g, TableWrap as _, Td as v, TextArea as y, TextInput as b, Th as x, enumOptions as S, fmtDate as C, fmtMinutesAsHours as w } from "../components/ui.js";
|
|
3
3
|
import { useState as T } from "react";
|
|
4
|
-
import { CheckCircle2 as E,
|
|
5
|
-
import { Fragment as
|
|
6
|
-
import { Button as
|
|
4
|
+
import { CheckCircle2 as E, CircleSlash as D, Clock as O, ListChecks as k, Plus as A, Send as j, Wallet as M } from "lucide-react";
|
|
5
|
+
import { Fragment as N, jsx as P, jsxs as F } from "react/jsx-runtime";
|
|
6
|
+
import { Button as I } from "@burdenoff/fe-libs/ui";
|
|
7
7
|
//#region src/proserviceworld/pages/TimePage.tsx
|
|
8
|
-
var
|
|
8
|
+
var L = [
|
|
9
9
|
"DRAFT",
|
|
10
10
|
"SUBMITTED",
|
|
11
11
|
"APPROVED",
|
|
12
12
|
"REJECTED",
|
|
13
13
|
"BILLED"
|
|
14
|
-
],
|
|
15
|
-
|
|
14
|
+
], R = ({ billable: e }) => e ? /* @__PURE__ */ F("span", {
|
|
15
|
+
className: "inline-flex items-center gap-1.5 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text",
|
|
16
|
+
children: [/* @__PURE__ */ P("span", { className: "h-1.5 w-1.5 rounded-full bg-current opacity-70" }), "Billable"]
|
|
17
|
+
}) : /* @__PURE__ */ F("span", {
|
|
18
|
+
className: "inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-tertiary",
|
|
19
|
+
children: [/* @__PURE__ */ P("span", { className: "h-1.5 w-1.5 rounded-full bg-current opacity-60" }), "Non-billable"]
|
|
20
|
+
}), z = () => {
|
|
21
|
+
let [t, n] = T(""), [r, l] = T(!1), [f, y] = T(null), { data: b, isLoading: N, error: z, refetch: V } = a({ status: t || void 0 }), H = i(), U = e(), W = (b?.items ?? []).reduce((e, t) => e + t.minutes, 0), G = (b?.items ?? []).filter((e) => e.billable).reduce((e, t) => e + t.minutes, 0), K = W > 0 ? Math.round(G / W * 100) : 0, q = (e, t, n) => e.mutate(t, {
|
|
16
22
|
onSuccess: () => y({
|
|
17
23
|
tone: "success",
|
|
18
24
|
text: `Time entry ${n}.`
|
|
@@ -22,114 +28,125 @@ var P = [
|
|
|
22
28
|
text: e instanceof Error ? e.message : "Failed."
|
|
23
29
|
})
|
|
24
30
|
});
|
|
25
|
-
return /* @__PURE__ */
|
|
31
|
+
return /* @__PURE__ */ F("div", {
|
|
26
32
|
className: "space-y-6",
|
|
27
33
|
children: [
|
|
28
|
-
/* @__PURE__ */
|
|
34
|
+
/* @__PURE__ */ P(m, {
|
|
29
35
|
title: "Time",
|
|
30
36
|
subtitle: "Capture expert effort against engagements and matters — the raw material of billing and utilization.",
|
|
31
|
-
icon: /* @__PURE__ */
|
|
32
|
-
actions: /* @__PURE__ */
|
|
37
|
+
icon: /* @__PURE__ */ P(O, { className: "h-6 w-6" }),
|
|
38
|
+
actions: /* @__PURE__ */ F(I, {
|
|
33
39
|
onClick: () => l(!0),
|
|
34
|
-
children: [/* @__PURE__ */
|
|
40
|
+
children: [/* @__PURE__ */ P(A, { className: "mr-1.5 h-4 w-4" }), "Log time"]
|
|
35
41
|
})
|
|
36
42
|
}),
|
|
37
|
-
f && /* @__PURE__ */
|
|
43
|
+
f && /* @__PURE__ */ P(o, {
|
|
38
44
|
tone: f.tone,
|
|
39
45
|
onClose: () => y(null),
|
|
40
46
|
children: f.text
|
|
41
47
|
}),
|
|
42
|
-
/* @__PURE__ */
|
|
48
|
+
/* @__PURE__ */ F("div", {
|
|
43
49
|
className: "grid grid-cols-2 gap-3 sm:grid-cols-4",
|
|
44
50
|
children: [
|
|
45
|
-
/* @__PURE__ */
|
|
51
|
+
/* @__PURE__ */ P(d, {
|
|
46
52
|
label: "Entries",
|
|
47
|
-
value: b?.totalCount ?? 0
|
|
53
|
+
value: b?.totalCount ?? 0,
|
|
54
|
+
icon: /* @__PURE__ */ P(k, { className: "h-4 w-4" })
|
|
48
55
|
}),
|
|
49
|
-
/* @__PURE__ */
|
|
56
|
+
/* @__PURE__ */ P(d, {
|
|
50
57
|
label: "Total hours",
|
|
51
|
-
value: w(
|
|
58
|
+
value: w(W),
|
|
59
|
+
icon: /* @__PURE__ */ P(O, { className: "h-4 w-4" })
|
|
52
60
|
}),
|
|
53
|
-
/* @__PURE__ */
|
|
61
|
+
/* @__PURE__ */ P(d, {
|
|
54
62
|
label: "Billable hours",
|
|
55
|
-
value: w(
|
|
63
|
+
value: w(G),
|
|
64
|
+
hint: `${K}% utilization`,
|
|
65
|
+
icon: /* @__PURE__ */ P(M, { className: "h-4 w-4" }),
|
|
56
66
|
tone: "accent"
|
|
57
67
|
}),
|
|
58
|
-
/* @__PURE__ */
|
|
68
|
+
/* @__PURE__ */ P(d, {
|
|
59
69
|
label: "Non-billable",
|
|
60
|
-
value: w(
|
|
70
|
+
value: w(W - G),
|
|
71
|
+
icon: /* @__PURE__ */ P(D, { className: "h-4 w-4" })
|
|
61
72
|
})
|
|
62
73
|
]
|
|
63
74
|
}),
|
|
64
|
-
/* @__PURE__ */
|
|
75
|
+
/* @__PURE__ */ P("div", {
|
|
65
76
|
className: "flex justify-end",
|
|
66
|
-
children: /* @__PURE__ */
|
|
77
|
+
children: /* @__PURE__ */ F(p, {
|
|
67
78
|
value: t,
|
|
68
79
|
onChange: (e) => n(e.target.value),
|
|
69
80
|
className: "sm:w-52",
|
|
70
|
-
children: [/* @__PURE__ */
|
|
81
|
+
children: [/* @__PURE__ */ P("option", {
|
|
71
82
|
value: "",
|
|
72
83
|
children: "All statuses"
|
|
73
|
-
}), S(
|
|
84
|
+
}), S(L)]
|
|
74
85
|
})
|
|
75
86
|
}),
|
|
76
|
-
|
|
77
|
-
error:
|
|
78
|
-
onRetry: () => void
|
|
79
|
-
}) : !b || b.items.length === 0 ? /* @__PURE__ */
|
|
87
|
+
N ? /* @__PURE__ */ P(u, {}) : z ? /* @__PURE__ */ P(c, {
|
|
88
|
+
error: z,
|
|
89
|
+
onRetry: () => void V()
|
|
90
|
+
}) : !b || b.items.length === 0 ? /* @__PURE__ */ P(s, {
|
|
80
91
|
title: "No time logged",
|
|
81
92
|
description: "Log your first time entry against an engagement or matter.",
|
|
82
|
-
icon: /* @__PURE__ */
|
|
83
|
-
action: /* @__PURE__ */
|
|
93
|
+
icon: /* @__PURE__ */ P(O, { className: "h-8 w-8" }),
|
|
94
|
+
action: /* @__PURE__ */ F(I, {
|
|
84
95
|
onClick: () => l(!0),
|
|
85
|
-
children: [/* @__PURE__ */
|
|
96
|
+
children: [/* @__PURE__ */ P(A, { className: "mr-1.5 h-4 w-4" }), "Log time"]
|
|
86
97
|
})
|
|
87
|
-
}) : /* @__PURE__ */
|
|
98
|
+
}) : /* @__PURE__ */ P(h, {
|
|
88
99
|
title: "Time entries",
|
|
89
|
-
children: /* @__PURE__ */
|
|
90
|
-
/* @__PURE__ */
|
|
91
|
-
/* @__PURE__ */
|
|
92
|
-
/* @__PURE__ */
|
|
93
|
-
/* @__PURE__ */
|
|
94
|
-
/* @__PURE__ */
|
|
95
|
-
/* @__PURE__ */
|
|
100
|
+
children: /* @__PURE__ */ F(_, { children: [/* @__PURE__ */ P("thead", { children: /* @__PURE__ */ F("tr", { children: [
|
|
101
|
+
/* @__PURE__ */ P(x, { children: "Date" }),
|
|
102
|
+
/* @__PURE__ */ P(x, { children: "Narrative" }),
|
|
103
|
+
/* @__PURE__ */ P(x, { children: "Hours" }),
|
|
104
|
+
/* @__PURE__ */ P(x, { children: "Billable" }),
|
|
105
|
+
/* @__PURE__ */ P(x, { children: "Status" }),
|
|
106
|
+
/* @__PURE__ */ P(x, {
|
|
96
107
|
className: "text-right",
|
|
97
108
|
children: "Actions"
|
|
98
109
|
})
|
|
99
|
-
] }) }), /* @__PURE__ */
|
|
100
|
-
className: "hover:bg-bg-sunken/
|
|
110
|
+
] }) }), /* @__PURE__ */ P("tbody", { children: b.items.map((e) => /* @__PURE__ */ F("tr", {
|
|
111
|
+
className: "transition-colors hover:bg-bg-sunken/60",
|
|
101
112
|
children: [
|
|
102
|
-
/* @__PURE__ */
|
|
103
|
-
|
|
113
|
+
/* @__PURE__ */ P(v, {
|
|
114
|
+
className: "whitespace-nowrap",
|
|
115
|
+
children: C(e.date)
|
|
116
|
+
}),
|
|
117
|
+
/* @__PURE__ */ P(v, {
|
|
104
118
|
className: "max-w-xs truncate text-text-primary",
|
|
105
119
|
children: e.narrative || "—"
|
|
106
120
|
}),
|
|
107
|
-
/* @__PURE__ */
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
121
|
+
/* @__PURE__ */ P(v, {
|
|
122
|
+
className: "whitespace-nowrap font-medium text-text-primary",
|
|
123
|
+
children: w(e.minutes)
|
|
124
|
+
}),
|
|
125
|
+
/* @__PURE__ */ P(v, { children: /* @__PURE__ */ P(R, { billable: e.billable }) }),
|
|
126
|
+
/* @__PURE__ */ P(v, { children: /* @__PURE__ */ P(g, { status: e.status }) }),
|
|
127
|
+
/* @__PURE__ */ P(v, {
|
|
111
128
|
className: "text-right",
|
|
112
|
-
children: /* @__PURE__ */
|
|
129
|
+
children: /* @__PURE__ */ F("div", {
|
|
113
130
|
className: "flex justify-end gap-1",
|
|
114
|
-
children: [e.status === "DRAFT" && /* @__PURE__ */
|
|
131
|
+
children: [e.status === "DRAFT" && /* @__PURE__ */ F(I, {
|
|
115
132
|
variant: "ghost",
|
|
116
133
|
size: "sm",
|
|
117
|
-
disabled:
|
|
118
|
-
onClick: () => H
|
|
119
|
-
children: [/* @__PURE__ */ j
|
|
120
|
-
}), e.status === "SUBMITTED" && /* @__PURE__ */
|
|
134
|
+
disabled: H.isPending,
|
|
135
|
+
onClick: () => q(H, e.id, "submitted"),
|
|
136
|
+
children: [/* @__PURE__ */ P(j, { className: "mr-1 h-3.5 w-3.5" }), "Submit"]
|
|
137
|
+
}), e.status === "SUBMITTED" && /* @__PURE__ */ F(I, {
|
|
121
138
|
variant: "ghost",
|
|
122
139
|
size: "sm",
|
|
123
|
-
disabled:
|
|
124
|
-
onClick: () =>
|
|
125
|
-
children: [/* @__PURE__ */
|
|
140
|
+
disabled: U.isPending,
|
|
141
|
+
onClick: () => q(U, e.id, "approved"),
|
|
142
|
+
children: [/* @__PURE__ */ P(E, { className: "mr-1 h-3.5 w-3.5" }), "Approve"]
|
|
126
143
|
})]
|
|
127
144
|
})
|
|
128
145
|
})
|
|
129
146
|
]
|
|
130
147
|
}, e.id)) })] })
|
|
131
148
|
}),
|
|
132
|
-
r && /* @__PURE__ */
|
|
149
|
+
r && /* @__PURE__ */ P(B, {
|
|
133
150
|
onClose: () => l(!1),
|
|
134
151
|
onDone: () => y({
|
|
135
152
|
tone: "success",
|
|
@@ -138,7 +155,7 @@ var P = [
|
|
|
138
155
|
})
|
|
139
156
|
]
|
|
140
157
|
});
|
|
141
|
-
},
|
|
158
|
+
}, B = ({ onClose: e, onDone: i }) => {
|
|
142
159
|
let a = n(), s = t({ pagination: { limit: 100 } }), c = r({ pagination: { limit: 100 } }), [u, d] = T({
|
|
143
160
|
against: "engagement",
|
|
144
161
|
engagementId: "",
|
|
@@ -153,16 +170,16 @@ var P = [
|
|
|
153
170
|
...n,
|
|
154
171
|
[e]: t.target.value
|
|
155
172
|
}));
|
|
156
|
-
return /* @__PURE__ */
|
|
173
|
+
return /* @__PURE__ */ P(f, {
|
|
157
174
|
open: !0,
|
|
158
175
|
onClose: e,
|
|
159
176
|
title: "Log time",
|
|
160
|
-
footer: /* @__PURE__ */
|
|
177
|
+
footer: /* @__PURE__ */ F(N, { children: [/* @__PURE__ */ P(I, {
|
|
161
178
|
variant: "outline",
|
|
162
179
|
onClick: e,
|
|
163
180
|
disabled: a.isPending,
|
|
164
181
|
children: "Cancel"
|
|
165
|
-
}), /* @__PURE__ */
|
|
182
|
+
}), /* @__PURE__ */ P(I, {
|
|
166
183
|
onClick: () => {
|
|
167
184
|
h(null);
|
|
168
185
|
let t = (Number(u.hours) || 0) * 60 + (Number(u.minutes) || 0);
|
|
@@ -187,70 +204,70 @@ var P = [
|
|
|
187
204
|
disabled: a.isPending,
|
|
188
205
|
children: a.isPending ? "Logging…" : "Log time"
|
|
189
206
|
})] }),
|
|
190
|
-
children: /* @__PURE__ */
|
|
207
|
+
children: /* @__PURE__ */ F("div", {
|
|
191
208
|
className: "space-y-3",
|
|
192
209
|
children: [
|
|
193
|
-
m && /* @__PURE__ */
|
|
210
|
+
m && /* @__PURE__ */ P(o, {
|
|
194
211
|
tone: "danger",
|
|
195
212
|
children: m
|
|
196
213
|
}),
|
|
197
|
-
/* @__PURE__ */
|
|
214
|
+
/* @__PURE__ */ P(l, {
|
|
198
215
|
label: "Against",
|
|
199
|
-
children: /* @__PURE__ */
|
|
216
|
+
children: /* @__PURE__ */ F(p, {
|
|
200
217
|
value: u.against,
|
|
201
218
|
onChange: g("against"),
|
|
202
|
-
children: [/* @__PURE__ */
|
|
219
|
+
children: [/* @__PURE__ */ P("option", {
|
|
203
220
|
value: "engagement",
|
|
204
221
|
children: "Engagement"
|
|
205
|
-
}), /* @__PURE__ */
|
|
222
|
+
}), /* @__PURE__ */ P("option", {
|
|
206
223
|
value: "matter",
|
|
207
224
|
children: "Matter"
|
|
208
225
|
})]
|
|
209
226
|
})
|
|
210
227
|
}),
|
|
211
|
-
u.against === "engagement" ? /* @__PURE__ */
|
|
228
|
+
u.against === "engagement" ? /* @__PURE__ */ P(l, {
|
|
212
229
|
label: "Engagement",
|
|
213
230
|
required: !0,
|
|
214
|
-
children: /* @__PURE__ */
|
|
231
|
+
children: /* @__PURE__ */ F(p, {
|
|
215
232
|
value: u.engagementId,
|
|
216
233
|
onChange: g("engagementId"),
|
|
217
|
-
children: [/* @__PURE__ */
|
|
234
|
+
children: [/* @__PURE__ */ P("option", {
|
|
218
235
|
value: "",
|
|
219
236
|
children: "Select an engagement…"
|
|
220
|
-
}), s.data?.items.map((e) => /* @__PURE__ */
|
|
237
|
+
}), s.data?.items.map((e) => /* @__PURE__ */ P("option", {
|
|
221
238
|
value: e.id,
|
|
222
239
|
children: e.name
|
|
223
240
|
}, e.id))]
|
|
224
241
|
})
|
|
225
|
-
}) : /* @__PURE__ */
|
|
242
|
+
}) : /* @__PURE__ */ P(l, {
|
|
226
243
|
label: "Matter",
|
|
227
244
|
required: !0,
|
|
228
|
-
children: /* @__PURE__ */
|
|
245
|
+
children: /* @__PURE__ */ F(p, {
|
|
229
246
|
value: u.matterId,
|
|
230
247
|
onChange: g("matterId"),
|
|
231
|
-
children: [/* @__PURE__ */
|
|
248
|
+
children: [/* @__PURE__ */ P("option", {
|
|
232
249
|
value: "",
|
|
233
250
|
children: "Select a matter…"
|
|
234
|
-
}), c.data?.items.map((e) => /* @__PURE__ */
|
|
251
|
+
}), c.data?.items.map((e) => /* @__PURE__ */ P("option", {
|
|
235
252
|
value: e.id,
|
|
236
253
|
children: e.title
|
|
237
254
|
}, e.id))]
|
|
238
255
|
})
|
|
239
256
|
}),
|
|
240
|
-
/* @__PURE__ */
|
|
257
|
+
/* @__PURE__ */ F("div", {
|
|
241
258
|
className: "grid grid-cols-3 gap-3",
|
|
242
259
|
children: [
|
|
243
|
-
/* @__PURE__ */
|
|
260
|
+
/* @__PURE__ */ P(l, {
|
|
244
261
|
label: "Date",
|
|
245
|
-
children: /* @__PURE__ */
|
|
262
|
+
children: /* @__PURE__ */ P(b, {
|
|
246
263
|
type: "date",
|
|
247
264
|
value: u.date,
|
|
248
265
|
onChange: g("date")
|
|
249
266
|
})
|
|
250
267
|
}),
|
|
251
|
-
/* @__PURE__ */
|
|
268
|
+
/* @__PURE__ */ P(l, {
|
|
252
269
|
label: "Hours",
|
|
253
|
-
children: /* @__PURE__ */
|
|
270
|
+
children: /* @__PURE__ */ P(b, {
|
|
254
271
|
type: "number",
|
|
255
272
|
min: "0",
|
|
256
273
|
value: u.hours,
|
|
@@ -258,9 +275,9 @@ var P = [
|
|
|
258
275
|
placeholder: "2"
|
|
259
276
|
})
|
|
260
277
|
}),
|
|
261
|
-
/* @__PURE__ */
|
|
278
|
+
/* @__PURE__ */ P(l, {
|
|
262
279
|
label: "Minutes",
|
|
263
|
-
children: /* @__PURE__ */
|
|
280
|
+
children: /* @__PURE__ */ P(b, {
|
|
264
281
|
type: "number",
|
|
265
282
|
min: "0",
|
|
266
283
|
max: "59",
|
|
@@ -271,9 +288,9 @@ var P = [
|
|
|
271
288
|
})
|
|
272
289
|
]
|
|
273
290
|
}),
|
|
274
|
-
/* @__PURE__ */
|
|
291
|
+
/* @__PURE__ */ P(l, {
|
|
275
292
|
label: "Rate (per hour)",
|
|
276
|
-
children: /* @__PURE__ */
|
|
293
|
+
children: /* @__PURE__ */ P(b, {
|
|
277
294
|
type: "number",
|
|
278
295
|
min: "0",
|
|
279
296
|
value: u.rate,
|
|
@@ -281,20 +298,23 @@ var P = [
|
|
|
281
298
|
placeholder: "450"
|
|
282
299
|
})
|
|
283
300
|
}),
|
|
284
|
-
/* @__PURE__ */
|
|
285
|
-
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
286
|
-
children: [/* @__PURE__ */
|
|
301
|
+
/* @__PURE__ */ F("label", {
|
|
302
|
+
className: "flex items-center gap-2.5 rounded-lg border border-border-seam bg-bg-canvas px-3 py-2.5 text-sm text-text-secondary",
|
|
303
|
+
children: [/* @__PURE__ */ P("input", {
|
|
287
304
|
type: "checkbox",
|
|
288
305
|
checked: u.billable,
|
|
289
306
|
onChange: (e) => d((t) => ({
|
|
290
307
|
...t,
|
|
291
308
|
billable: e.target.checked
|
|
292
309
|
}))
|
|
293
|
-
}), "
|
|
310
|
+
}), /* @__PURE__ */ F("span", {
|
|
311
|
+
className: "flex items-center gap-1.5",
|
|
312
|
+
children: [/* @__PURE__ */ P(M, { className: "h-3.5 w-3.5 text-status-success-text" }), "Billable"]
|
|
313
|
+
})]
|
|
294
314
|
}),
|
|
295
|
-
/* @__PURE__ */
|
|
315
|
+
/* @__PURE__ */ P(l, {
|
|
296
316
|
label: "Narrative",
|
|
297
|
-
children: /* @__PURE__ */
|
|
317
|
+
children: /* @__PURE__ */ P(y, {
|
|
298
318
|
value: u.narrative,
|
|
299
319
|
onChange: g("narrative"),
|
|
300
320
|
placeholder: "Reviewed working papers and drafted findings memo…"
|
|
@@ -305,6 +325,6 @@ var P = [
|
|
|
305
325
|
});
|
|
306
326
|
};
|
|
307
327
|
//#endregion
|
|
308
|
-
export {
|
|
328
|
+
export { z as TimePage };
|
|
309
329
|
|
|
310
330
|
//# sourceMappingURL=TimePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimePage.js","names":[],"sources":["../../../src/proserviceworld/pages/TimePage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus, Clock, Send, CheckCircle2 } from 'lucide-react';\nimport {\n useTimeEntries,\n useLogTimeEntry,\n useSubmitTimeEntry,\n useApproveTimeEntry,\n useEngagements,\n useMatters,\n type PswTimeEntryStatus,\n} from '../hooks/useProServiceWorldApi';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n NativeSelect,\n Banner,\n MetricCard,\n TableWrap,\n Th,\n Td,\n enumOptions,\n fmtDate,\n fmtMinutesAsHours,\n} from '../components/ui';\n\nconst STATUSES: PswTimeEntryStatus[] = ['DRAFT', 'SUBMITTED', 'APPROVED', 'REJECTED', 'BILLED'];\n\nexport const TimePage: FC = () => {\n const [statusFilter, setStatusFilter] = useState<PswTimeEntryStatus | ''>('');\n const [logging, setLogging] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n const { data, isLoading, error, refetch } = useTimeEntries({ status: statusFilter || undefined });\n const submit = useSubmitTimeEntry();\n const approve = useApproveTimeEntry();\n\n const totalMinutes = (data?.items ?? []).reduce((s, t) => s + t.minutes, 0);\n const billableMinutes = (data?.items ?? [])\n .filter((t) => t.billable)\n .reduce((s, t) => s + t.minutes, 0);\n\n const act = (\n fn: {\n mutate: (id: string, opts: { onSuccess: () => void; onError: (e: unknown) => void }) => void;\n },\n id: string,\n verb: string\n ) =>\n fn.mutate(id, {\n onSuccess: () => setBanner({ tone: 'success', text: `Time entry ${verb}.` }),\n onError: (e) =>\n setBanner({ tone: 'danger', text: e instanceof Error ? e.message : 'Failed.' }),\n });\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Time\"\n subtitle=\"Capture expert effort against engagements and matters — the raw material of billing and utilization.\"\n icon={<Clock className=\"h-6 w-6\" />}\n actions={\n <Button onClick={() => setLogging(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Log time\n </Button>\n }\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\n </Banner>\n )}\n\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <MetricCard label=\"Entries\" value={data?.totalCount ?? 0} />\n <MetricCard label=\"Total hours\" value={fmtMinutesAsHours(totalMinutes)} />\n <MetricCard\n label=\"Billable hours\"\n value={fmtMinutesAsHours(billableMinutes)}\n tone=\"accent\"\n />\n <MetricCard\n label=\"Non-billable\"\n value={fmtMinutesAsHours(totalMinutes - billableMinutes)}\n />\n </div>\n\n <div className=\"flex justify-end\">\n <NativeSelect\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as PswTimeEntryStatus | '')}\n className=\"sm:w-52\"\n >\n <option value=\"\">All statuses</option>\n {enumOptions(STATUSES)}\n </NativeSelect>\n </div>\n\n {isLoading ? (\n <LoadingBlock />\n ) : error ? (\n <ErrorState error={error} onRetry={() => void refetch()} />\n ) : !data || data.items.length === 0 ? (\n <Empty\n title=\"No time logged\"\n description=\"Log your first time entry against an engagement or matter.\"\n icon={<Clock className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setLogging(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Log time\n </Button>\n }\n />\n ) : (\n <SectionCard title=\"Time entries\">\n <TableWrap>\n <thead>\n <tr>\n <Th>Date</Th>\n <Th>Narrative</Th>\n <Th>Hours</Th>\n <Th>Billable</Th>\n <Th>Status</Th>\n <Th className=\"text-right\">Actions</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((t) => (\n <tr key={t.id} className=\"hover:bg-bg-sunken/50\">\n <Td>{fmtDate(t.date)}</Td>\n <Td className=\"max-w-xs truncate text-text-primary\">{t.narrative || '—'}</Td>\n <Td>{fmtMinutesAsHours(t.minutes)}</Td>\n <Td>{t.billable ? 'Yes' : 'No'}</Td>\n <Td>\n <StatusPill status={t.status} />\n </Td>\n <Td className=\"text-right\">\n <div className=\"flex justify-end gap-1\">\n {t.status === 'DRAFT' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={submit.isPending}\n onClick={() => act(submit, t.id, 'submitted')}\n >\n <Send className=\"mr-1 h-3.5 w-3.5\" />\n Submit\n </Button>\n )}\n {t.status === 'SUBMITTED' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={approve.isPending}\n onClick={() => act(approve, t.id, 'approved')}\n >\n <CheckCircle2 className=\"mr-1 h-3.5 w-3.5\" />\n Approve\n </Button>\n )}\n </div>\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </SectionCard>\n )}\n\n {logging && (\n <LogTimeModal\n onClose={() => setLogging(false)}\n onDone={() => setBanner({ tone: 'success', text: 'Time logged.' })}\n />\n )}\n </div>\n );\n};\n\nconst LogTimeModal: FC<{ onClose: () => void; onDone: () => void }> = ({ onClose, onDone }) => {\n const log = useLogTimeEntry();\n const engagements = useEngagements({ pagination: { limit: 100 } });\n const matters = useMatters({ pagination: { limit: 100 } });\n const [form, setForm] = useState({\n against: 'engagement',\n engagementId: '',\n matterId: '',\n date: new Date().toISOString().slice(0, 10),\n hours: '',\n minutes: '',\n rate: '',\n billable: true,\n narrative: '',\n });\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof typeof form) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n\n const submit = () => {\n setErr(null);\n const totalMinutes = (Number(form.hours) || 0) * 60 + (Number(form.minutes) || 0);\n if (totalMinutes <= 0) return setErr('Enter the time spent.');\n const ref =\n form.against === 'engagement'\n ? { engagementId: form.engagementId }\n : { matterId: form.matterId };\n if (form.against === 'engagement' && !form.engagementId) return setErr('Select an engagement.');\n if (form.against === 'matter' && !form.matterId) return setErr('Select a matter.');\n log.mutate(\n {\n ...ref,\n date: new Date(form.date).toISOString(),\n minutes: totalMinutes,\n rate: form.rate ? Number(form.rate) : 0,\n billable: form.billable,\n narrative: form.narrative.trim() || null,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Log time\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={log.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={log.isPending}>\n {log.isPending ? 'Logging…' : 'Log time'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Against\">\n <NativeSelect value={form.against} onChange={set('against')}>\n <option value=\"engagement\">Engagement</option>\n <option value=\"matter\">Matter</option>\n </NativeSelect>\n </Field>\n {form.against === 'engagement' ? (\n <Field label=\"Engagement\" required>\n <NativeSelect value={form.engagementId} onChange={set('engagementId')}>\n <option value=\"\">Select an engagement…</option>\n {engagements.data?.items.map((e) => (\n <option key={e.id} value={e.id}>\n {e.name}\n </option>\n ))}\n </NativeSelect>\n </Field>\n ) : (\n <Field label=\"Matter\" required>\n <NativeSelect value={form.matterId} onChange={set('matterId')}>\n <option value=\"\">Select a matter…</option>\n {matters.data?.items.map((m) => (\n <option key={m.id} value={m.id}>\n {m.title}\n </option>\n ))}\n </NativeSelect>\n </Field>\n )}\n <div className=\"grid grid-cols-3 gap-3\">\n <Field label=\"Date\">\n <TextInput type=\"date\" value={form.date} onChange={set('date')} />\n </Field>\n <Field label=\"Hours\">\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.hours}\n onChange={set('hours')}\n placeholder=\"2\"\n />\n </Field>\n <Field label=\"Minutes\">\n <TextInput\n type=\"number\"\n min=\"0\"\n max=\"59\"\n value={form.minutes}\n onChange={set('minutes')}\n placeholder=\"30\"\n />\n </Field>\n </div>\n <Field label=\"Rate (per hour)\">\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.rate}\n onChange={set('rate')}\n placeholder=\"450\"\n />\n </Field>\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={form.billable}\n onChange={(e) => setForm((f) => ({ ...f, billable: e.target.checked }))}\n />\n Billable\n </label>\n <Field label=\"Narrative\">\n <TextArea\n value={form.narrative}\n onChange={set('narrative')}\n placeholder=\"Reviewed working papers and drafted findings memo…\"\n />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TimePage.js","names":[],"sources":["../../../src/proserviceworld/pages/TimePage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus, Clock, Send, CheckCircle2, ListChecks, Wallet, CircleSlash } from 'lucide-react';\nimport {\n useTimeEntries,\n useLogTimeEntry,\n useSubmitTimeEntry,\n useApproveTimeEntry,\n useEngagements,\n useMatters,\n type PswTimeEntryStatus,\n} from '../hooks/useProServiceWorldApi';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n NativeSelect,\n Banner,\n MetricCard,\n TableWrap,\n Th,\n Td,\n enumOptions,\n fmtDate,\n fmtMinutesAsHours,\n} from '../components/ui';\n\nconst STATUSES: PswTimeEntryStatus[] = ['DRAFT', 'SUBMITTED', 'APPROVED', 'REJECTED', 'BILLED'];\n\n/** Compact billable / non-billable tag. */\nconst BillableTag: FC<{ billable: boolean }> = ({ billable }) =>\n billable ? (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current opacity-70\" />\n Billable\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-tertiary\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current opacity-60\" />\n Non-billable\n </span>\n );\n\nexport const TimePage: FC = () => {\n const [statusFilter, setStatusFilter] = useState<PswTimeEntryStatus | ''>('');\n const [logging, setLogging] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n const { data, isLoading, error, refetch } = useTimeEntries({ status: statusFilter || undefined });\n const submit = useSubmitTimeEntry();\n const approve = useApproveTimeEntry();\n\n const totalMinutes = (data?.items ?? []).reduce((s, t) => s + t.minutes, 0);\n const billableMinutes = (data?.items ?? [])\n .filter((t) => t.billable)\n .reduce((s, t) => s + t.minutes, 0);\n const utilization = totalMinutes > 0 ? Math.round((billableMinutes / totalMinutes) * 100) : 0;\n\n const act = (\n fn: {\n mutate: (id: string, opts: { onSuccess: () => void; onError: (e: unknown) => void }) => void;\n },\n id: string,\n verb: string\n ) =>\n fn.mutate(id, {\n onSuccess: () => setBanner({ tone: 'success', text: `Time entry ${verb}.` }),\n onError: (e) =>\n setBanner({ tone: 'danger', text: e instanceof Error ? e.message : 'Failed.' }),\n });\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Time\"\n subtitle=\"Capture expert effort against engagements and matters — the raw material of billing and utilization.\"\n icon={<Clock className=\"h-6 w-6\" />}\n actions={\n <Button onClick={() => setLogging(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Log time\n </Button>\n }\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\n </Banner>\n )}\n\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <MetricCard\n label=\"Entries\"\n value={data?.totalCount ?? 0}\n icon={<ListChecks className=\"h-4 w-4\" />}\n />\n <MetricCard\n label=\"Total hours\"\n value={fmtMinutesAsHours(totalMinutes)}\n icon={<Clock className=\"h-4 w-4\" />}\n />\n <MetricCard\n label=\"Billable hours\"\n value={fmtMinutesAsHours(billableMinutes)}\n hint={`${utilization}% utilization`}\n icon={<Wallet className=\"h-4 w-4\" />}\n tone=\"accent\"\n />\n <MetricCard\n label=\"Non-billable\"\n value={fmtMinutesAsHours(totalMinutes - billableMinutes)}\n icon={<CircleSlash className=\"h-4 w-4\" />}\n />\n </div>\n\n <div className=\"flex justify-end\">\n <NativeSelect\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as PswTimeEntryStatus | '')}\n className=\"sm:w-52\"\n >\n <option value=\"\">All statuses</option>\n {enumOptions(STATUSES)}\n </NativeSelect>\n </div>\n\n {isLoading ? (\n <LoadingBlock />\n ) : error ? (\n <ErrorState error={error} onRetry={() => void refetch()} />\n ) : !data || data.items.length === 0 ? (\n <Empty\n title=\"No time logged\"\n description=\"Log your first time entry against an engagement or matter.\"\n icon={<Clock className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setLogging(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Log time\n </Button>\n }\n />\n ) : (\n <SectionCard title=\"Time entries\">\n <TableWrap>\n <thead>\n <tr>\n <Th>Date</Th>\n <Th>Narrative</Th>\n <Th>Hours</Th>\n <Th>Billable</Th>\n <Th>Status</Th>\n <Th className=\"text-right\">Actions</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((t) => (\n <tr key={t.id} className=\"transition-colors hover:bg-bg-sunken/60\">\n <Td className=\"whitespace-nowrap\">{fmtDate(t.date)}</Td>\n <Td className=\"max-w-xs truncate text-text-primary\">{t.narrative || '—'}</Td>\n <Td className=\"whitespace-nowrap font-medium text-text-primary\">\n {fmtMinutesAsHours(t.minutes)}\n </Td>\n <Td>\n <BillableTag billable={t.billable} />\n </Td>\n <Td>\n <StatusPill status={t.status} />\n </Td>\n <Td className=\"text-right\">\n <div className=\"flex justify-end gap-1\">\n {t.status === 'DRAFT' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={submit.isPending}\n onClick={() => act(submit, t.id, 'submitted')}\n >\n <Send className=\"mr-1 h-3.5 w-3.5\" />\n Submit\n </Button>\n )}\n {t.status === 'SUBMITTED' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={approve.isPending}\n onClick={() => act(approve, t.id, 'approved')}\n >\n <CheckCircle2 className=\"mr-1 h-3.5 w-3.5\" />\n Approve\n </Button>\n )}\n </div>\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </SectionCard>\n )}\n\n {logging && (\n <LogTimeModal\n onClose={() => setLogging(false)}\n onDone={() => setBanner({ tone: 'success', text: 'Time logged.' })}\n />\n )}\n </div>\n );\n};\n\nconst LogTimeModal: FC<{ onClose: () => void; onDone: () => void }> = ({ onClose, onDone }) => {\n const log = useLogTimeEntry();\n const engagements = useEngagements({ pagination: { limit: 100 } });\n const matters = useMatters({ pagination: { limit: 100 } });\n const [form, setForm] = useState({\n against: 'engagement',\n engagementId: '',\n matterId: '',\n date: new Date().toISOString().slice(0, 10),\n hours: '',\n minutes: '',\n rate: '',\n billable: true,\n narrative: '',\n });\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof typeof form) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n\n const submit = () => {\n setErr(null);\n const totalMinutes = (Number(form.hours) || 0) * 60 + (Number(form.minutes) || 0);\n if (totalMinutes <= 0) return setErr('Enter the time spent.');\n const ref =\n form.against === 'engagement'\n ? { engagementId: form.engagementId }\n : { matterId: form.matterId };\n if (form.against === 'engagement' && !form.engagementId) return setErr('Select an engagement.');\n if (form.against === 'matter' && !form.matterId) return setErr('Select a matter.');\n log.mutate(\n {\n ...ref,\n date: new Date(form.date).toISOString(),\n minutes: totalMinutes,\n rate: form.rate ? Number(form.rate) : 0,\n billable: form.billable,\n narrative: form.narrative.trim() || null,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Log time\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={log.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={log.isPending}>\n {log.isPending ? 'Logging…' : 'Log time'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Against\">\n <NativeSelect value={form.against} onChange={set('against')}>\n <option value=\"engagement\">Engagement</option>\n <option value=\"matter\">Matter</option>\n </NativeSelect>\n </Field>\n {form.against === 'engagement' ? (\n <Field label=\"Engagement\" required>\n <NativeSelect value={form.engagementId} onChange={set('engagementId')}>\n <option value=\"\">Select an engagement…</option>\n {engagements.data?.items.map((e) => (\n <option key={e.id} value={e.id}>\n {e.name}\n </option>\n ))}\n </NativeSelect>\n </Field>\n ) : (\n <Field label=\"Matter\" required>\n <NativeSelect value={form.matterId} onChange={set('matterId')}>\n <option value=\"\">Select a matter…</option>\n {matters.data?.items.map((m) => (\n <option key={m.id} value={m.id}>\n {m.title}\n </option>\n ))}\n </NativeSelect>\n </Field>\n )}\n <div className=\"grid grid-cols-3 gap-3\">\n <Field label=\"Date\">\n <TextInput type=\"date\" value={form.date} onChange={set('date')} />\n </Field>\n <Field label=\"Hours\">\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.hours}\n onChange={set('hours')}\n placeholder=\"2\"\n />\n </Field>\n <Field label=\"Minutes\">\n <TextInput\n type=\"number\"\n min=\"0\"\n max=\"59\"\n value={form.minutes}\n onChange={set('minutes')}\n placeholder=\"30\"\n />\n </Field>\n </div>\n <Field label=\"Rate (per hour)\">\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.rate}\n onChange={set('rate')}\n placeholder=\"450\"\n />\n </Field>\n <label className=\"flex items-center gap-2.5 rounded-lg border border-border-seam bg-bg-canvas px-3 py-2.5 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={form.billable}\n onChange={(e) => setForm((f) => ({ ...f, billable: e.target.checked }))}\n />\n <span className=\"flex items-center gap-1.5\">\n <Wallet className=\"h-3.5 w-3.5 text-status-success-text\" />\n Billable\n </span>\n </label>\n <Field label=\"Narrative\">\n <TextArea\n value={form.narrative}\n onChange={set('narrative')}\n placeholder=\"Reviewed working papers and drafted findings memo…\"\n />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
|