@burdenoff/microfe-proserviceworld 2026.1014.1 → 2026.1026.1

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 (28) hide show
  1. package/dist/proserviceworld/hooks/useProServiceWorldApi.d.ts +17 -0
  2. package/dist/proserviceworld/hooks/useProServiceWorldApi.d.ts.map +1 -1
  3. package/dist/proserviceworld/hooks/useProServiceWorldApi.js +70 -48
  4. package/dist/proserviceworld/hooks/useProServiceWorldApi.js.map +1 -1
  5. package/dist/proserviceworld/hooks/useTimeTimer.d.ts +24 -0
  6. package/dist/proserviceworld/hooks/useTimeTimer.d.ts.map +1 -0
  7. package/dist/proserviceworld/hooks/useTimeTimer.js +58 -0
  8. package/dist/proserviceworld/hooks/useTimeTimer.js.map +1 -0
  9. package/dist/proserviceworld/pages/CompliancePage.d.ts.map +1 -1
  10. package/dist/proserviceworld/pages/CompliancePage.js +136 -118
  11. package/dist/proserviceworld/pages/CompliancePage.js.map +1 -1
  12. package/dist/proserviceworld/pages/ConflictsPage.d.ts.map +1 -1
  13. package/dist/proserviceworld/pages/ConflictsPage.js +87 -71
  14. package/dist/proserviceworld/pages/ConflictsPage.js.map +1 -1
  15. package/dist/proserviceworld/pages/DocumentsPage.d.ts.map +1 -1
  16. package/dist/proserviceworld/pages/DocumentsPage.js +104 -86
  17. package/dist/proserviceworld/pages/DocumentsPage.js.map +1 -1
  18. package/dist/proserviceworld/pages/InvoicesPage.d.ts.map +1 -1
  19. package/dist/proserviceworld/pages/InvoicesPage.js +122 -104
  20. package/dist/proserviceworld/pages/InvoicesPage.js.map +1 -1
  21. package/dist/proserviceworld/pages/TimePage.d.ts +7 -0
  22. package/dist/proserviceworld/pages/TimePage.d.ts.map +1 -1
  23. package/dist/proserviceworld/pages/TimePage.js +527 -189
  24. package/dist/proserviceworld/pages/TimePage.js.map +1 -1
  25. package/dist/proserviceworld/pages/TrustPage.d.ts.map +1 -1
  26. package/dist/proserviceworld/pages/TrustPage.js +226 -207
  27. package/dist/proserviceworld/pages/TrustPage.js.map +1 -1
  28. package/package.json +1 -1
@@ -1,322 +1,660 @@
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
- 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
- import { useState as T } from "react";
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";
1
+ import { useApproveTimeEntry as e, useEngagements as t, useLogTimeEntry as n, useMatters as r, useRejectTimeEntry as i, useSubmitTimeEntry as a, useTimeEntries as o, useUpdateTimeEntry as s } from "../hooks/useProServiceWorldApi.js";
2
+ import { Banner as c, Empty as l, ErrorState as u, Field as d, LoadingBlock as f, MetricCard as p, Modal as m, NativeSelect as h, PageHeader as ee, SectionCard as te, StatusPill as ne, TableWrap as re, Td as g, TextArea as _, TextInput as v, Th as y, enumOptions as ie, fmtDate as ae, fmtMinutesAsHours as b } from "../components/ui.js";
3
+ import { useAnalytics as oe } from "../hooks/useAnalytics.js";
4
+ import { elapsedMinutes as x, formatTimerDuration as S, useTimeTimer as se } from "../hooks/useTimeTimer.js";
5
+ import { useDeferredValue as C, useState as w } from "react";
6
+ import { CheckCircle2 as ce, CircleSlash as le, Clock as T, ListChecks as ue, Play as E, Plus as D, Search as de, Send as fe, Square as O, Trash2 as k, Wallet as A } from "lucide-react";
7
+ import { Fragment as j, jsx as M, jsxs as N } from "react/jsx-runtime";
8
+ import { Button as P } from "@burdenoff/fe-libs/ui";
7
9
  //#region src/proserviceworld/pages/TimePage.tsx
8
- var L = [
10
+ var pe = [
9
11
  "DRAFT",
10
12
  "SUBMITTED",
11
13
  "APPROVED",
12
14
  "REJECTED",
13
15
  "BILLED"
14
- ], R = ({ billable: e }) => e ? /* @__PURE__ */ F("span", {
16
+ ], me = ({ billable: e }) => e ? /* @__PURE__ */ N("span", {
15
17
  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
+ children: [/* @__PURE__ */ M("span", { className: "h-1.5 w-1.5 rounded-full bg-current opacity-70" }), "Billable"]
19
+ }) : /* @__PURE__ */ N("span", {
18
20
  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, {
22
- onSuccess: () => y({
21
+ children: [/* @__PURE__ */ M("span", { className: "h-1.5 w-1.5 rounded-full bg-current opacity-60" }), "Non-billable"]
22
+ }), F = () => {
23
+ let [t, n] = w(""), [r, d] = w(""), m = C(r), [_, x] = w(!1), [S, O] = w(!1), [k, F] = w(null), [z, B] = w(null), { data: V, isLoading: _e, error: H, refetch: ve } = o({
24
+ status: t || void 0,
25
+ search: m.trim() || void 0
26
+ }), { emitFact: ye } = oe(), U = a(), W = e(), be = s(), G = i(), [K, q] = w(null), [J, Y] = w(null), X = se(), Z = (V?.items ?? []).reduce((e, t) => e + t.minutes, 0), Q = (V?.items ?? []).filter((e) => e.billable).reduce((e, t) => e + t.minutes, 0), xe = Z > 0 ? Math.round(Q / Z * 100) : 0, $ = (e, t, n) => e.mutate(t, {
27
+ onSuccess: () => B({
23
28
  tone: "success",
24
29
  text: `Time entry ${n}.`
25
30
  }),
26
- onError: (e) => y({
31
+ onError: (e) => B({
27
32
  tone: "danger",
28
33
  text: e instanceof Error ? e.message : "Failed."
29
34
  })
30
35
  });
31
- return /* @__PURE__ */ F("div", {
36
+ return /* @__PURE__ */ N("div", {
32
37
  className: "space-y-6",
33
38
  children: [
34
- /* @__PURE__ */ P(m, {
39
+ /* @__PURE__ */ M(ee, {
35
40
  title: "Time",
36
41
  subtitle: "Capture expert effort against engagements and matters — the raw material of billing and utilization.",
37
- icon: /* @__PURE__ */ P(O, { className: "h-6 w-6" }),
38
- actions: /* @__PURE__ */ F(I, {
39
- onClick: () => l(!0),
40
- children: [/* @__PURE__ */ P(A, { className: "mr-1.5 h-4 w-4" }), "Log time"]
41
- })
42
+ icon: /* @__PURE__ */ M(T, { className: "h-6 w-6" }),
43
+ actions: X.timer ? /* @__PURE__ */ M(R, {
44
+ seconds: X.seconds,
45
+ onStop: () => {
46
+ let e = X.stop();
47
+ e && (F(e), x(!0));
48
+ },
49
+ onDiscard: X.discard
50
+ }) : /* @__PURE__ */ N(j, { children: [/* @__PURE__ */ N(P, {
51
+ variant: "outline",
52
+ onClick: () => O(!0),
53
+ children: [/* @__PURE__ */ M(E, { className: "mr-1.5 h-4 w-4" }), "Start timer"]
54
+ }), /* @__PURE__ */ N(P, {
55
+ onClick: () => x(!0),
56
+ children: [/* @__PURE__ */ M(D, { className: "mr-1.5 h-4 w-4" }), "Log time"]
57
+ })] })
42
58
  }),
43
- f && /* @__PURE__ */ P(o, {
44
- tone: f.tone,
45
- onClose: () => y(null),
46
- children: f.text
59
+ z && /* @__PURE__ */ M(c, {
60
+ tone: z.tone,
61
+ onClose: () => B(null),
62
+ children: z.text
47
63
  }),
48
- /* @__PURE__ */ F("div", {
64
+ /* @__PURE__ */ N("div", {
49
65
  className: "grid grid-cols-2 gap-3 sm:grid-cols-4",
50
66
  children: [
51
- /* @__PURE__ */ P(d, {
67
+ /* @__PURE__ */ M(p, {
52
68
  label: "Entries",
53
- value: b?.totalCount ?? 0,
54
- icon: /* @__PURE__ */ P(k, { className: "h-4 w-4" })
69
+ value: V?.totalCount ?? 0,
70
+ icon: /* @__PURE__ */ M(ue, { className: "h-4 w-4" })
55
71
  }),
56
- /* @__PURE__ */ P(d, {
72
+ /* @__PURE__ */ M(p, {
57
73
  label: "Total hours",
58
- value: w(W),
59
- icon: /* @__PURE__ */ P(O, { className: "h-4 w-4" })
74
+ value: b(Z),
75
+ icon: /* @__PURE__ */ M(T, { className: "h-4 w-4" })
60
76
  }),
61
- /* @__PURE__ */ P(d, {
77
+ /* @__PURE__ */ M(p, {
62
78
  label: "Billable hours",
63
- value: w(G),
64
- hint: `${K}% utilization`,
65
- icon: /* @__PURE__ */ P(M, { className: "h-4 w-4" }),
79
+ value: b(Q),
80
+ hint: `${xe}% utilization`,
81
+ icon: /* @__PURE__ */ M(A, { className: "h-4 w-4" }),
66
82
  tone: "accent"
67
83
  }),
68
- /* @__PURE__ */ P(d, {
84
+ /* @__PURE__ */ M(p, {
69
85
  label: "Non-billable",
70
- value: w(W - G),
71
- icon: /* @__PURE__ */ P(D, { className: "h-4 w-4" })
86
+ value: b(Z - Q),
87
+ icon: /* @__PURE__ */ M(le, { className: "h-4 w-4" })
72
88
  })
73
89
  ]
74
90
  }),
75
- /* @__PURE__ */ P("div", {
76
- className: "flex justify-end",
77
- children: /* @__PURE__ */ F(p, {
91
+ /* @__PURE__ */ N("div", {
92
+ className: "flex flex-col gap-2 sm:flex-row sm:items-center",
93
+ children: [/* @__PURE__ */ N("div", {
94
+ className: "relative flex-1",
95
+ children: [/* @__PURE__ */ M(de, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary" }), /* @__PURE__ */ M(v, {
96
+ placeholder: "Search time entries…",
97
+ value: r,
98
+ onChange: (e) => d(e.target.value),
99
+ onKeyDown: (e) => {
100
+ e.key === "Enter" && r.trim() && ye("psw.search.performed", {
101
+ entityType: "time-entry",
102
+ queryLength: r.trim().length
103
+ });
104
+ },
105
+ className: "pl-9"
106
+ })]
107
+ }), /* @__PURE__ */ N(h, {
78
108
  value: t,
79
109
  onChange: (e) => n(e.target.value),
80
110
  className: "sm:w-52",
81
- children: [/* @__PURE__ */ P("option", {
111
+ children: [/* @__PURE__ */ M("option", {
82
112
  value: "",
83
113
  children: "All statuses"
84
- }), S(L)]
85
- })
114
+ }), ie(pe)]
115
+ })]
86
116
  }),
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, {
117
+ _e ? /* @__PURE__ */ M(f, {}) : H ? /* @__PURE__ */ M(u, {
118
+ error: H,
119
+ onRetry: () => void ve()
120
+ }) : !V || V.items.length === 0 ? /* @__PURE__ */ M(l, {
91
121
  title: "No time logged",
92
122
  description: "Log your first time entry against an engagement or matter.",
93
- icon: /* @__PURE__ */ P(O, { className: "h-8 w-8" }),
94
- action: /* @__PURE__ */ F(I, {
95
- onClick: () => l(!0),
96
- children: [/* @__PURE__ */ P(A, { className: "mr-1.5 h-4 w-4" }), "Log time"]
123
+ icon: /* @__PURE__ */ M(T, { className: "h-8 w-8" }),
124
+ action: /* @__PURE__ */ N(P, {
125
+ onClick: () => x(!0),
126
+ children: [/* @__PURE__ */ M(D, { className: "mr-1.5 h-4 w-4" }), "Log time"]
97
127
  })
98
- }) : /* @__PURE__ */ P(h, {
128
+ }) : /* @__PURE__ */ M(te, {
99
129
  title: "Time entries",
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, {
130
+ children: /* @__PURE__ */ N(re, { children: [/* @__PURE__ */ M("thead", { children: /* @__PURE__ */ N("tr", { children: [
131
+ /* @__PURE__ */ M(y, { children: "Date" }),
132
+ /* @__PURE__ */ M(y, { children: "Narrative" }),
133
+ /* @__PURE__ */ M(y, { children: "Hours" }),
134
+ /* @__PURE__ */ M(y, { children: "Billable" }),
135
+ /* @__PURE__ */ M(y, { children: "Status" }),
136
+ /* @__PURE__ */ M(y, {
107
137
  className: "text-right",
108
138
  children: "Actions"
109
139
  })
110
- ] }) }), /* @__PURE__ */ P("tbody", { children: b.items.map((e) => /* @__PURE__ */ F("tr", {
140
+ ] }) }), /* @__PURE__ */ M("tbody", { children: V.items.map((e) => /* @__PURE__ */ N("tr", {
111
141
  className: "transition-colors hover:bg-bg-sunken/60",
112
142
  children: [
113
- /* @__PURE__ */ P(v, {
143
+ /* @__PURE__ */ M(g, {
114
144
  className: "whitespace-nowrap",
115
- children: C(e.date)
145
+ children: ae(e.date)
116
146
  }),
117
- /* @__PURE__ */ P(v, {
147
+ /* @__PURE__ */ M(g, {
118
148
  className: "max-w-xs truncate text-text-primary",
119
149
  children: e.narrative || "—"
120
150
  }),
121
- /* @__PURE__ */ P(v, {
151
+ /* @__PURE__ */ M(g, {
122
152
  className: "whitespace-nowrap font-medium text-text-primary",
123
- children: w(e.minutes)
153
+ children: b(e.minutes)
124
154
  }),
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, {
155
+ /* @__PURE__ */ M(g, { children: /* @__PURE__ */ M(me, { billable: e.billable }) }),
156
+ /* @__PURE__ */ M(g, { children: /* @__PURE__ */ M(ne, { status: e.status }) }),
157
+ /* @__PURE__ */ M(g, {
128
158
  className: "text-right",
129
- children: /* @__PURE__ */ F("div", {
159
+ children: /* @__PURE__ */ N("div", {
130
160
  className: "flex justify-end gap-1",
131
- children: [e.status === "DRAFT" && /* @__PURE__ */ F(I, {
132
- variant: "ghost",
133
- size: "sm",
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, {
138
- variant: "ghost",
139
- size: "sm",
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"]
143
- })]
161
+ children: [
162
+ e.status === "DRAFT" && /* @__PURE__ */ N(P, {
163
+ variant: "ghost",
164
+ size: "sm",
165
+ disabled: U.isPending,
166
+ onClick: () => $(U, e.id, "submitted"),
167
+ children: [/* @__PURE__ */ M(fe, { className: "mr-1 h-3.5 w-3.5" }), "Submit"]
168
+ }),
169
+ [
170
+ "DRAFT",
171
+ "SUBMITTED",
172
+ "REJECTED"
173
+ ].includes(e.status) && /* @__PURE__ */ M(P, {
174
+ variant: "ghost",
175
+ size: "sm",
176
+ onClick: () => q(e),
177
+ children: "Edit"
178
+ }),
179
+ e.status === "SUBMITTED" && /* @__PURE__ */ N(P, {
180
+ variant: "ghost",
181
+ size: "sm",
182
+ disabled: W.isPending,
183
+ onClick: () => $(W, e.id, "approved"),
184
+ children: [/* @__PURE__ */ M(ce, { className: "mr-1 h-3.5 w-3.5" }), "Approve"]
185
+ }),
186
+ e.status === "SUBMITTED" && /* @__PURE__ */ M(P, {
187
+ variant: "ghost",
188
+ size: "sm",
189
+ disabled: G.isPending,
190
+ onClick: () => Y(e),
191
+ children: "Reject"
192
+ })
193
+ ]
144
194
  })
145
195
  })
146
196
  ]
147
197
  }, e.id)) })] })
148
198
  }),
149
- r && /* @__PURE__ */ P(B, {
150
- onClose: () => l(!1),
151
- onDone: () => y({
152
- tone: "success",
153
- text: "Time logged."
154
- })
199
+ _ && /* @__PURE__ */ M(ge, {
200
+ initialTimer: k,
201
+ onClose: () => x(!1),
202
+ onDone: () => {
203
+ F(null), B({
204
+ tone: "success",
205
+ text: "Time logged."
206
+ });
207
+ }
208
+ }),
209
+ K && /* @__PURE__ */ M(I, {
210
+ entry: K,
211
+ mutation: be,
212
+ onClose: () => q(null)
213
+ }),
214
+ J && /* @__PURE__ */ M(L, {
215
+ entry: J,
216
+ mutation: G,
217
+ onClose: () => Y(null)
218
+ }),
219
+ S && /* @__PURE__ */ M(he, {
220
+ onClose: () => O(!1),
221
+ onStart: (e) => {
222
+ X.start(e), O(!1), B({
223
+ tone: "success",
224
+ text: "Timer started."
225
+ });
226
+ }
155
227
  })
156
228
  ]
157
229
  });
158
- }, B = ({ onClose: e, onDone: i }) => {
159
- let a = n(), s = t({ pagination: { limit: 100 } }), c = r({ pagination: { limit: 100 } }), [u, d] = T({
160
- against: "engagement",
161
- engagementId: "",
162
- matterId: "",
163
- date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
164
- hours: "",
165
- minutes: "",
166
- rate: "",
167
- billable: !0,
168
- narrative: ""
169
- }), [m, h] = T(null), g = (e) => (t) => d((n) => ({
230
+ }, I = ({ entry: e, mutation: t, onClose: n }) => {
231
+ let [r, i] = w({
232
+ date: e.date.slice(0, 10),
233
+ minutes: String(e.minutes),
234
+ rate: String(e.rate),
235
+ narrative: e.narrative ?? "",
236
+ billable: e.billable
237
+ }), [a, o] = w(null);
238
+ return /* @__PURE__ */ M(m, {
239
+ open: !0,
240
+ onClose: n,
241
+ title: "Edit time entry",
242
+ footer: /* @__PURE__ */ N(j, { children: [/* @__PURE__ */ M(P, {
243
+ variant: "ghost",
244
+ onClick: n,
245
+ children: "Cancel"
246
+ }), /* @__PURE__ */ M(P, {
247
+ onClick: () => {
248
+ let i = Number(r.minutes);
249
+ if (!Number.isInteger(i) || i <= 0) return o("Enter positive minutes.");
250
+ t.mutate({
251
+ id: e.id,
252
+ input: {
253
+ date: new Date(r.date).toISOString(),
254
+ minutes: i,
255
+ rate: Number(r.rate) || 0,
256
+ narrative: r.narrative.trim() || null,
257
+ billable: r.billable
258
+ }
259
+ }, {
260
+ onSuccess: n,
261
+ onError: (e) => o(e instanceof Error ? e.message : "Failed to save.")
262
+ });
263
+ },
264
+ disabled: t.isPending,
265
+ children: "Save"
266
+ })] }),
267
+ children: /* @__PURE__ */ N("div", {
268
+ className: "space-y-4",
269
+ children: [
270
+ a && /* @__PURE__ */ M(c, {
271
+ tone: "danger",
272
+ children: a
273
+ }),
274
+ /* @__PURE__ */ M(d, {
275
+ label: "Date",
276
+ children: /* @__PURE__ */ M(v, {
277
+ type: "date",
278
+ value: r.date,
279
+ onChange: (e) => i({
280
+ ...r,
281
+ date: e.target.value
282
+ })
283
+ })
284
+ }),
285
+ /* @__PURE__ */ M(d, {
286
+ label: "Minutes",
287
+ children: /* @__PURE__ */ M(v, {
288
+ type: "number",
289
+ min: 1,
290
+ value: r.minutes,
291
+ onChange: (e) => i({
292
+ ...r,
293
+ minutes: e.target.value
294
+ })
295
+ })
296
+ }),
297
+ /* @__PURE__ */ M(d, {
298
+ label: "Rate",
299
+ children: /* @__PURE__ */ M(v, {
300
+ type: "number",
301
+ min: 0,
302
+ value: r.rate,
303
+ onChange: (e) => i({
304
+ ...r,
305
+ rate: e.target.value
306
+ })
307
+ })
308
+ }),
309
+ /* @__PURE__ */ M(d, {
310
+ label: "Narrative",
311
+ children: /* @__PURE__ */ M(_, {
312
+ value: r.narrative,
313
+ onChange: (e) => i({
314
+ ...r,
315
+ narrative: e.target.value
316
+ })
317
+ })
318
+ }),
319
+ /* @__PURE__ */ N("label", {
320
+ className: "flex items-center gap-2 text-sm",
321
+ children: [
322
+ /* @__PURE__ */ M("input", {
323
+ type: "checkbox",
324
+ checked: r.billable,
325
+ onChange: (e) => i({
326
+ ...r,
327
+ billable: e.target.checked
328
+ })
329
+ }),
330
+ " ",
331
+ "Billable"
332
+ ]
333
+ })
334
+ ]
335
+ })
336
+ });
337
+ }, L = ({ entry: e, mutation: t, onClose: n }) => {
338
+ let [r, i] = w(""), [a, o] = w(null);
339
+ return /* @__PURE__ */ M(m, {
340
+ open: !0,
341
+ onClose: n,
342
+ title: "Reject time entry",
343
+ footer: /* @__PURE__ */ N(j, { children: [/* @__PURE__ */ M(P, {
344
+ variant: "ghost",
345
+ onClick: n,
346
+ children: "Cancel"
347
+ }), /* @__PURE__ */ M(P, {
348
+ onClick: () => {
349
+ if (!r.trim()) return o("Enter a rejection reason.");
350
+ t.mutate({
351
+ id: e.id,
352
+ reason: r.trim()
353
+ }, {
354
+ onSuccess: n,
355
+ onError: (e) => o(e instanceof Error ? e.message : "Failed to reject.")
356
+ });
357
+ },
358
+ disabled: t.isPending,
359
+ children: "Reject"
360
+ })] }),
361
+ children: /* @__PURE__ */ N("div", {
362
+ className: "space-y-4",
363
+ children: [a && /* @__PURE__ */ M(c, {
364
+ tone: "danger",
365
+ children: a
366
+ }), /* @__PURE__ */ M(d, {
367
+ label: "Reason",
368
+ children: /* @__PURE__ */ M(_, {
369
+ autoFocus: !0,
370
+ value: r,
371
+ onChange: (e) => i(e.target.value),
372
+ placeholder: "Explain what needs correction."
373
+ })
374
+ })]
375
+ })
376
+ });
377
+ }, R = ({ seconds: e, onStop: t, onDiscard: n }) => /* @__PURE__ */ N("div", {
378
+ className: "flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-2 py-1",
379
+ children: [
380
+ /* @__PURE__ */ M("span", {
381
+ className: "font-mono text-sm font-semibold tabular-nums text-text-primary",
382
+ "aria-live": "polite",
383
+ children: S(e)
384
+ }),
385
+ /* @__PURE__ */ N(P, {
386
+ size: "sm",
387
+ onClick: t,
388
+ children: [/* @__PURE__ */ M(O, { className: "mr-1 h-3.5 w-3.5 fill-current" }), "Stop"]
389
+ }),
390
+ /* @__PURE__ */ M(P, {
391
+ variant: "ghost",
392
+ size: "sm",
393
+ onClick: n,
394
+ "aria-label": "Discard timer",
395
+ children: /* @__PURE__ */ M(k, { className: "h-3.5 w-3.5" })
396
+ })
397
+ ]
398
+ }), z = () => ({
399
+ against: "engagement",
400
+ engagementId: "",
401
+ matterId: "",
402
+ date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
403
+ hours: "",
404
+ minutes: "",
405
+ rate: "",
406
+ billable: !0,
407
+ narrative: ""
408
+ }), B = (e, t = Date.now()) => {
409
+ let n = x(e, t);
410
+ return {
411
+ ...e,
412
+ hours: String(Math.floor(n / 60)),
413
+ minutes: String(n % 60)
414
+ };
415
+ }, V = ({ form: e, set: n }) => {
416
+ let i = t({ pagination: { limit: 100 } }), a = r({ pagination: { limit: 100 } });
417
+ return /* @__PURE__ */ N(j, { children: [/* @__PURE__ */ M(d, {
418
+ label: "Against",
419
+ children: /* @__PURE__ */ N(h, {
420
+ value: e.against,
421
+ onChange: n("against"),
422
+ children: [/* @__PURE__ */ M("option", {
423
+ value: "engagement",
424
+ children: "Engagement"
425
+ }), /* @__PURE__ */ M("option", {
426
+ value: "matter",
427
+ children: "Matter"
428
+ })]
429
+ })
430
+ }), e.against === "engagement" ? /* @__PURE__ */ M(d, {
431
+ label: "Engagement",
432
+ required: !0,
433
+ children: /* @__PURE__ */ N(h, {
434
+ value: e.engagementId,
435
+ onChange: n("engagementId"),
436
+ children: [/* @__PURE__ */ M("option", {
437
+ value: "",
438
+ children: "Select an engagement…"
439
+ }), i.data?.items.map((e) => /* @__PURE__ */ M("option", {
440
+ value: e.id,
441
+ children: e.name
442
+ }, e.id))]
443
+ })
444
+ }) : /* @__PURE__ */ M(d, {
445
+ label: "Matter",
446
+ required: !0,
447
+ children: /* @__PURE__ */ N(h, {
448
+ value: e.matterId,
449
+ onChange: n("matterId"),
450
+ children: [/* @__PURE__ */ M("option", {
451
+ value: "",
452
+ children: "Select a matter…"
453
+ }), a.data?.items.map((e) => /* @__PURE__ */ M("option", {
454
+ value: e.id,
455
+ children: e.title
456
+ }, e.id))]
457
+ })
458
+ })] });
459
+ }, he = ({ onClose: e, onStart: t }) => {
460
+ let [n, r] = w(z), [i, a] = w(null), o = (e) => (t) => r((n) => ({
170
461
  ...n,
171
462
  [e]: t.target.value
172
463
  }));
173
- return /* @__PURE__ */ P(f, {
464
+ return /* @__PURE__ */ M(m, {
174
465
  open: !0,
175
466
  onClose: e,
176
- title: "Log time",
177
- footer: /* @__PURE__ */ F(N, { children: [/* @__PURE__ */ P(I, {
467
+ title: "Start timer",
468
+ footer: /* @__PURE__ */ N(j, { children: [/* @__PURE__ */ M(P, {
178
469
  variant: "outline",
179
470
  onClick: e,
180
- disabled: a.isPending,
181
471
  children: "Cancel"
182
- }), /* @__PURE__ */ P(I, {
472
+ }), /* @__PURE__ */ N(P, {
183
473
  onClick: () => {
184
- h(null);
185
- let t = (Number(u.hours) || 0) * 60 + (Number(u.minutes) || 0);
186
- if (t <= 0) return h("Enter the time spent.");
187
- let n = u.against === "engagement" ? { engagementId: u.engagementId } : { matterId: u.matterId };
188
- if (u.against === "engagement" && !u.engagementId) return h("Select an engagement.");
189
- if (u.against === "matter" && !u.matterId) return h("Select a matter.");
190
- a.mutate({
474
+ if (n.against === "engagement" && !n.engagementId) return a("Select an engagement.");
475
+ if (n.against === "matter" && !n.matterId) return a("Select a matter.");
476
+ t({
477
+ against: n.against,
478
+ engagementId: n.engagementId,
479
+ matterId: n.matterId,
480
+ date: n.date,
481
+ rate: n.rate,
482
+ billable: n.billable,
483
+ narrative: n.narrative
484
+ });
485
+ },
486
+ children: [/* @__PURE__ */ M(E, { className: "mr-1.5 h-4 w-4" }), "Start timer"]
487
+ })] }),
488
+ children: /* @__PURE__ */ N("div", {
489
+ className: "space-y-3",
490
+ children: [
491
+ i && /* @__PURE__ */ M(c, {
492
+ tone: "danger",
493
+ children: i
494
+ }),
495
+ /* @__PURE__ */ M(V, {
496
+ form: n,
497
+ set: o
498
+ }),
499
+ /* @__PURE__ */ M(d, {
500
+ label: "Rate (per hour)",
501
+ children: /* @__PURE__ */ M(v, {
502
+ type: "number",
503
+ min: "0",
504
+ value: n.rate,
505
+ onChange: o("rate"),
506
+ placeholder: "450"
507
+ })
508
+ }),
509
+ /* @__PURE__ */ N("label", {
510
+ 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",
511
+ children: [/* @__PURE__ */ M("input", {
512
+ type: "checkbox",
513
+ checked: n.billable,
514
+ onChange: (e) => r((t) => ({
515
+ ...t,
516
+ billable: e.target.checked
517
+ }))
518
+ }), /* @__PURE__ */ M("span", { children: "Billable" })]
519
+ }),
520
+ /* @__PURE__ */ M(d, {
521
+ label: "Narrative",
522
+ children: /* @__PURE__ */ M(_, {
523
+ value: n.narrative,
524
+ onChange: o("narrative"),
525
+ placeholder: "Describe the work…"
526
+ })
527
+ })
528
+ ]
529
+ })
530
+ });
531
+ }, ge = ({ initialTimer: e, onClose: t, onDone: r }) => {
532
+ let i = n(), [a, o] = w(() => e ? B(e) : z()), [s, l] = w(null), u = (e) => (t) => o((n) => ({
533
+ ...n,
534
+ [e]: t.target.value
535
+ }));
536
+ return /* @__PURE__ */ M(m, {
537
+ open: !0,
538
+ onClose: t,
539
+ title: "Log time",
540
+ footer: /* @__PURE__ */ N(j, { children: [/* @__PURE__ */ M(P, {
541
+ variant: "outline",
542
+ onClick: t,
543
+ disabled: i.isPending,
544
+ children: "Cancel"
545
+ }), /* @__PURE__ */ M(P, {
546
+ onClick: () => {
547
+ l(null);
548
+ let e = (Number(a.hours) || 0) * 60 + (Number(a.minutes) || 0);
549
+ if (e <= 0) return l("Enter the time spent.");
550
+ let n = a.against === "engagement" ? { engagementId: a.engagementId } : { matterId: a.matterId };
551
+ if (a.against === "engagement" && !a.engagementId) return l("Select an engagement.");
552
+ if (a.against === "matter" && !a.matterId) return l("Select a matter.");
553
+ i.mutate({
191
554
  ...n,
192
- date: new Date(u.date).toISOString(),
193
- minutes: t,
194
- rate: u.rate ? Number(u.rate) : 0,
195
- billable: u.billable,
196
- narrative: u.narrative.trim() || null
555
+ date: new Date(a.date).toISOString(),
556
+ minutes: e,
557
+ rate: a.rate ? Number(a.rate) : 0,
558
+ billable: a.billable,
559
+ narrative: a.narrative.trim() || null
197
560
  }, {
198
561
  onSuccess: () => {
199
- i(), e();
562
+ r(), t();
200
563
  },
201
- onError: (e) => h(e instanceof Error ? e.message : "Failed.")
564
+ onError: (e) => l(e instanceof Error ? e.message : "Failed.")
202
565
  });
203
566
  },
204
- disabled: a.isPending,
205
- children: a.isPending ? "Logging…" : "Log time"
567
+ disabled: i.isPending,
568
+ children: i.isPending ? "Logging…" : "Log time"
206
569
  })] }),
207
- children: /* @__PURE__ */ F("div", {
570
+ children: /* @__PURE__ */ N("div", {
208
571
  className: "space-y-3",
209
572
  children: [
210
- m && /* @__PURE__ */ P(o, {
573
+ s && /* @__PURE__ */ M(c, {
211
574
  tone: "danger",
212
- children: m
575
+ children: s
213
576
  }),
214
- /* @__PURE__ */ P(l, {
577
+ /* @__PURE__ */ M(d, {
215
578
  label: "Against",
216
- children: /* @__PURE__ */ F(p, {
217
- value: u.against,
218
- onChange: g("against"),
219
- children: [/* @__PURE__ */ P("option", {
579
+ children: /* @__PURE__ */ N(h, {
580
+ value: a.against,
581
+ onChange: u("against"),
582
+ children: [/* @__PURE__ */ M("option", {
220
583
  value: "engagement",
221
584
  children: "Engagement"
222
- }), /* @__PURE__ */ P("option", {
585
+ }), /* @__PURE__ */ M("option", {
223
586
  value: "matter",
224
587
  children: "Matter"
225
588
  })]
226
589
  })
227
590
  }),
228
- u.against === "engagement" ? /* @__PURE__ */ P(l, {
229
- label: "Engagement",
230
- required: !0,
231
- children: /* @__PURE__ */ F(p, {
232
- value: u.engagementId,
233
- onChange: g("engagementId"),
234
- children: [/* @__PURE__ */ P("option", {
235
- value: "",
236
- children: "Select an engagement…"
237
- }), s.data?.items.map((e) => /* @__PURE__ */ P("option", {
238
- value: e.id,
239
- children: e.name
240
- }, e.id))]
241
- })
242
- }) : /* @__PURE__ */ P(l, {
243
- label: "Matter",
244
- required: !0,
245
- children: /* @__PURE__ */ F(p, {
246
- value: u.matterId,
247
- onChange: g("matterId"),
248
- children: [/* @__PURE__ */ P("option", {
249
- value: "",
250
- children: "Select a matter…"
251
- }), c.data?.items.map((e) => /* @__PURE__ */ P("option", {
252
- value: e.id,
253
- children: e.title
254
- }, e.id))]
255
- })
591
+ /* @__PURE__ */ M(V, {
592
+ form: a,
593
+ set: u
256
594
  }),
257
- /* @__PURE__ */ F("div", {
595
+ /* @__PURE__ */ N("div", {
258
596
  className: "grid grid-cols-3 gap-3",
259
597
  children: [
260
- /* @__PURE__ */ P(l, {
598
+ /* @__PURE__ */ M(d, {
261
599
  label: "Date",
262
- children: /* @__PURE__ */ P(b, {
600
+ children: /* @__PURE__ */ M(v, {
263
601
  type: "date",
264
- value: u.date,
265
- onChange: g("date")
602
+ value: a.date,
603
+ onChange: u("date")
266
604
  })
267
605
  }),
268
- /* @__PURE__ */ P(l, {
606
+ /* @__PURE__ */ M(d, {
269
607
  label: "Hours",
270
- children: /* @__PURE__ */ P(b, {
608
+ children: /* @__PURE__ */ M(v, {
271
609
  type: "number",
272
610
  min: "0",
273
- value: u.hours,
274
- onChange: g("hours"),
611
+ value: a.hours,
612
+ onChange: u("hours"),
275
613
  placeholder: "2"
276
614
  })
277
615
  }),
278
- /* @__PURE__ */ P(l, {
616
+ /* @__PURE__ */ M(d, {
279
617
  label: "Minutes",
280
- children: /* @__PURE__ */ P(b, {
618
+ children: /* @__PURE__ */ M(v, {
281
619
  type: "number",
282
620
  min: "0",
283
621
  max: "59",
284
- value: u.minutes,
285
- onChange: g("minutes"),
622
+ value: a.minutes,
623
+ onChange: u("minutes"),
286
624
  placeholder: "30"
287
625
  })
288
626
  })
289
627
  ]
290
628
  }),
291
- /* @__PURE__ */ P(l, {
629
+ /* @__PURE__ */ M(d, {
292
630
  label: "Rate (per hour)",
293
- children: /* @__PURE__ */ P(b, {
631
+ children: /* @__PURE__ */ M(v, {
294
632
  type: "number",
295
633
  min: "0",
296
- value: u.rate,
297
- onChange: g("rate"),
634
+ value: a.rate,
635
+ onChange: u("rate"),
298
636
  placeholder: "450"
299
637
  })
300
638
  }),
301
- /* @__PURE__ */ F("label", {
639
+ /* @__PURE__ */ N("label", {
302
640
  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", {
641
+ children: [/* @__PURE__ */ M("input", {
304
642
  type: "checkbox",
305
- checked: u.billable,
306
- onChange: (e) => d((t) => ({
643
+ checked: a.billable,
644
+ onChange: (e) => o((t) => ({
307
645
  ...t,
308
646
  billable: e.target.checked
309
647
  }))
310
- }), /* @__PURE__ */ F("span", {
648
+ }), /* @__PURE__ */ N("span", {
311
649
  className: "flex items-center gap-1.5",
312
- children: [/* @__PURE__ */ P(M, { className: "h-3.5 w-3.5 text-status-success-text" }), "Billable"]
650
+ children: [/* @__PURE__ */ M(A, { className: "h-3.5 w-3.5 text-status-success-text" }), "Billable"]
313
651
  })]
314
652
  }),
315
- /* @__PURE__ */ P(l, {
653
+ /* @__PURE__ */ M(d, {
316
654
  label: "Narrative",
317
- children: /* @__PURE__ */ P(y, {
318
- value: u.narrative,
319
- onChange: g("narrative"),
655
+ children: /* @__PURE__ */ M(_, {
656
+ value: a.narrative,
657
+ onChange: u("narrative"),
320
658
  placeholder: "Reviewed working papers and drafted findings memo…"
321
659
  })
322
660
  })
@@ -325,6 +663,6 @@ var L = [
325
663
  });
326
664
  };
327
665
  //#endregion
328
- export { z as TimePage };
666
+ export { F as TimePage, B as timerToLogForm };
329
667
 
330
668
  //# sourceMappingURL=TimePage.js.map