@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.
Files changed (26) hide show
  1. package/dist/proserviceworld/components/DiscussionThread.js +1 -1
  2. package/dist/proserviceworld/components/DiscussionThread.js.map +1 -1
  3. package/dist/proserviceworld/components/ui.js +18 -18
  4. package/dist/proserviceworld/components/ui.js.map +1 -1
  5. package/dist/proserviceworld/hooks/useEntityComments.js.map +1 -1
  6. package/dist/proserviceworld/pages/AnalyticsPage.js +277 -111
  7. package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
  8. package/dist/proserviceworld/pages/ClientDetailPage.js +219 -151
  9. package/dist/proserviceworld/pages/ClientDetailPage.js.map +1 -1
  10. package/dist/proserviceworld/pages/ClientsPage.js +148 -127
  11. package/dist/proserviceworld/pages/ClientsPage.js.map +1 -1
  12. package/dist/proserviceworld/pages/CompliancePage.js +140 -96
  13. package/dist/proserviceworld/pages/CompliancePage.js.map +1 -1
  14. package/dist/proserviceworld/pages/DashboardPage.js +221 -104
  15. package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
  16. package/dist/proserviceworld/pages/EngagementDetailPage.js +211 -181
  17. package/dist/proserviceworld/pages/EngagementDetailPage.js.map +1 -1
  18. package/dist/proserviceworld/pages/EngagementsPage.js +144 -106
  19. package/dist/proserviceworld/pages/EngagementsPage.js.map +1 -1
  20. package/dist/proserviceworld/pages/InvoicesPage.js +144 -129
  21. package/dist/proserviceworld/pages/InvoicesPage.js.map +1 -1
  22. package/dist/proserviceworld/pages/MattersPage.js +163 -122
  23. package/dist/proserviceworld/pages/MattersPage.js.map +1 -1
  24. package/dist/proserviceworld/pages/TimePage.js +112 -92
  25. package/dist/proserviceworld/pages/TimePage.js.map +1 -1
  26. 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, Clock as D, Plus as O, Send as k } from "lucide-react";
5
- import { Fragment as A, jsx as j, jsxs as M } from "react/jsx-runtime";
6
- import { Button as N } from "@burdenoff/fe-libs/ui";
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 P = [
8
+ var L = [
9
9
  "DRAFT",
10
10
  "SUBMITTED",
11
11
  "APPROVED",
12
12
  "REJECTED",
13
13
  "BILLED"
14
- ], F = () => {
15
- let [t, n] = T(""), [r, l] = T(!1), [f, y] = T(null), { data: b, isLoading: A, error: F, refetch: L } = a({ status: t || void 0 }), R = i(), z = e(), B = (b?.items ?? []).reduce((e, t) => e + t.minutes, 0), V = (b?.items ?? []).filter((e) => e.billable).reduce((e, t) => e + t.minutes, 0), H = (e, t, n) => e.mutate(t, {
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__ */ M("div", {
31
+ return /* @__PURE__ */ F("div", {
26
32
  className: "space-y-6",
27
33
  children: [
28
- /* @__PURE__ */ j(m, {
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__ */ j(D, { className: "h-6 w-6" }),
32
- actions: /* @__PURE__ */ M(N, {
37
+ icon: /* @__PURE__ */ P(O, { className: "h-6 w-6" }),
38
+ actions: /* @__PURE__ */ F(I, {
33
39
  onClick: () => l(!0),
34
- children: [/* @__PURE__ */ j(O, { className: "mr-1.5 h-4 w-4" }), "Log time"]
40
+ children: [/* @__PURE__ */ P(A, { className: "mr-1.5 h-4 w-4" }), "Log time"]
35
41
  })
36
42
  }),
37
- f && /* @__PURE__ */ j(o, {
43
+ f && /* @__PURE__ */ P(o, {
38
44
  tone: f.tone,
39
45
  onClose: () => y(null),
40
46
  children: f.text
41
47
  }),
42
- /* @__PURE__ */ M("div", {
48
+ /* @__PURE__ */ F("div", {
43
49
  className: "grid grid-cols-2 gap-3 sm:grid-cols-4",
44
50
  children: [
45
- /* @__PURE__ */ j(d, {
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__ */ j(d, {
56
+ /* @__PURE__ */ P(d, {
50
57
  label: "Total hours",
51
- value: w(B)
58
+ value: w(W),
59
+ icon: /* @__PURE__ */ P(O, { className: "h-4 w-4" })
52
60
  }),
53
- /* @__PURE__ */ j(d, {
61
+ /* @__PURE__ */ P(d, {
54
62
  label: "Billable hours",
55
- value: w(V),
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__ */ j(d, {
68
+ /* @__PURE__ */ P(d, {
59
69
  label: "Non-billable",
60
- value: w(B - V)
70
+ value: w(W - G),
71
+ icon: /* @__PURE__ */ P(D, { className: "h-4 w-4" })
61
72
  })
62
73
  ]
63
74
  }),
64
- /* @__PURE__ */ j("div", {
75
+ /* @__PURE__ */ P("div", {
65
76
  className: "flex justify-end",
66
- children: /* @__PURE__ */ M(p, {
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__ */ j("option", {
81
+ children: [/* @__PURE__ */ P("option", {
71
82
  value: "",
72
83
  children: "All statuses"
73
- }), S(P)]
84
+ }), S(L)]
74
85
  })
75
86
  }),
76
- A ? /* @__PURE__ */ j(u, {}) : F ? /* @__PURE__ */ j(c, {
77
- error: F,
78
- onRetry: () => void L()
79
- }) : !b || b.items.length === 0 ? /* @__PURE__ */ j(s, {
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__ */ j(D, { className: "h-8 w-8" }),
83
- action: /* @__PURE__ */ M(N, {
93
+ icon: /* @__PURE__ */ P(O, { className: "h-8 w-8" }),
94
+ action: /* @__PURE__ */ F(I, {
84
95
  onClick: () => l(!0),
85
- children: [/* @__PURE__ */ j(O, { className: "mr-1.5 h-4 w-4" }), "Log time"]
96
+ children: [/* @__PURE__ */ P(A, { className: "mr-1.5 h-4 w-4" }), "Log time"]
86
97
  })
87
- }) : /* @__PURE__ */ j(h, {
98
+ }) : /* @__PURE__ */ P(h, {
88
99
  title: "Time entries",
89
- children: /* @__PURE__ */ M(_, { children: [/* @__PURE__ */ j("thead", { children: /* @__PURE__ */ M("tr", { children: [
90
- /* @__PURE__ */ j(x, { children: "Date" }),
91
- /* @__PURE__ */ j(x, { children: "Narrative" }),
92
- /* @__PURE__ */ j(x, { children: "Hours" }),
93
- /* @__PURE__ */ j(x, { children: "Billable" }),
94
- /* @__PURE__ */ j(x, { children: "Status" }),
95
- /* @__PURE__ */ j(x, {
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__ */ j("tbody", { children: b.items.map((e) => /* @__PURE__ */ M("tr", {
100
- className: "hover:bg-bg-sunken/50",
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__ */ j(v, { children: C(e.date) }),
103
- /* @__PURE__ */ j(v, {
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__ */ j(v, { children: w(e.minutes) }),
108
- /* @__PURE__ */ j(v, { children: e.billable ? "Yes" : "No" }),
109
- /* @__PURE__ */ j(v, { children: /* @__PURE__ */ j(g, { status: e.status }) }),
110
- /* @__PURE__ */ j(v, {
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__ */ M("div", {
129
+ children: /* @__PURE__ */ F("div", {
113
130
  className: "flex justify-end gap-1",
114
- children: [e.status === "DRAFT" && /* @__PURE__ */ M(N, {
131
+ children: [e.status === "DRAFT" && /* @__PURE__ */ F(I, {
115
132
  variant: "ghost",
116
133
  size: "sm",
117
- disabled: R.isPending,
118
- onClick: () => H(R, e.id, "submitted"),
119
- children: [/* @__PURE__ */ j(k, { className: "mr-1 h-3.5 w-3.5" }), "Submit"]
120
- }), e.status === "SUBMITTED" && /* @__PURE__ */ M(N, {
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: z.isPending,
124
- onClick: () => H(z, e.id, "approved"),
125
- children: [/* @__PURE__ */ j(E, { className: "mr-1 h-3.5 w-3.5" }), "Approve"]
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__ */ j(I, {
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
- }, I = ({ onClose: e, onDone: i }) => {
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__ */ j(f, {
173
+ return /* @__PURE__ */ P(f, {
157
174
  open: !0,
158
175
  onClose: e,
159
176
  title: "Log time",
160
- footer: /* @__PURE__ */ M(A, { children: [/* @__PURE__ */ j(N, {
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__ */ j(N, {
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__ */ M("div", {
207
+ children: /* @__PURE__ */ F("div", {
191
208
  className: "space-y-3",
192
209
  children: [
193
- m && /* @__PURE__ */ j(o, {
210
+ m && /* @__PURE__ */ P(o, {
194
211
  tone: "danger",
195
212
  children: m
196
213
  }),
197
- /* @__PURE__ */ j(l, {
214
+ /* @__PURE__ */ P(l, {
198
215
  label: "Against",
199
- children: /* @__PURE__ */ M(p, {
216
+ children: /* @__PURE__ */ F(p, {
200
217
  value: u.against,
201
218
  onChange: g("against"),
202
- children: [/* @__PURE__ */ j("option", {
219
+ children: [/* @__PURE__ */ P("option", {
203
220
  value: "engagement",
204
221
  children: "Engagement"
205
- }), /* @__PURE__ */ j("option", {
222
+ }), /* @__PURE__ */ P("option", {
206
223
  value: "matter",
207
224
  children: "Matter"
208
225
  })]
209
226
  })
210
227
  }),
211
- u.against === "engagement" ? /* @__PURE__ */ j(l, {
228
+ u.against === "engagement" ? /* @__PURE__ */ P(l, {
212
229
  label: "Engagement",
213
230
  required: !0,
214
- children: /* @__PURE__ */ M(p, {
231
+ children: /* @__PURE__ */ F(p, {
215
232
  value: u.engagementId,
216
233
  onChange: g("engagementId"),
217
- children: [/* @__PURE__ */ j("option", {
234
+ children: [/* @__PURE__ */ P("option", {
218
235
  value: "",
219
236
  children: "Select an engagement…"
220
- }), s.data?.items.map((e) => /* @__PURE__ */ j("option", {
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__ */ j(l, {
242
+ }) : /* @__PURE__ */ P(l, {
226
243
  label: "Matter",
227
244
  required: !0,
228
- children: /* @__PURE__ */ M(p, {
245
+ children: /* @__PURE__ */ F(p, {
229
246
  value: u.matterId,
230
247
  onChange: g("matterId"),
231
- children: [/* @__PURE__ */ j("option", {
248
+ children: [/* @__PURE__ */ P("option", {
232
249
  value: "",
233
250
  children: "Select a matter…"
234
- }), c.data?.items.map((e) => /* @__PURE__ */ j("option", {
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__ */ M("div", {
257
+ /* @__PURE__ */ F("div", {
241
258
  className: "grid grid-cols-3 gap-3",
242
259
  children: [
243
- /* @__PURE__ */ j(l, {
260
+ /* @__PURE__ */ P(l, {
244
261
  label: "Date",
245
- children: /* @__PURE__ */ j(b, {
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__ */ j(l, {
268
+ /* @__PURE__ */ P(l, {
252
269
  label: "Hours",
253
- children: /* @__PURE__ */ j(b, {
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__ */ j(l, {
278
+ /* @__PURE__ */ P(l, {
262
279
  label: "Minutes",
263
- children: /* @__PURE__ */ j(b, {
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__ */ j(l, {
291
+ /* @__PURE__ */ P(l, {
275
292
  label: "Rate (per hour)",
276
- children: /* @__PURE__ */ j(b, {
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__ */ M("label", {
285
- className: "flex items-center gap-2 text-sm text-text-secondary",
286
- children: [/* @__PURE__ */ j("input", {
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
- }), "Billable"]
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__ */ j(l, {
315
+ /* @__PURE__ */ P(l, {
296
316
  label: "Narrative",
297
- children: /* @__PURE__ */ j(y, {
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 { F as TimePage };
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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-proserviceworld",
3
- "version": "2026.706.2",
3
+ "version": "2026.706.3",
4
4
  "description": "Burdenoff ProServiceWorld Microfrontend",
5
5
  "type": "module",
6
6
  "files": [