@burdenoff/microfe-proserviceworld 2026.1015.1 → 2026.1026.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,340 +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 { useAnalytics as T } from "../hooks/useAnalytics.js";
4
- import { useDeferredValue as E, useState as D } from "react";
5
- import { CheckCircle2 as O, CircleSlash as ee, Clock as k, ListChecks as A, Plus as j, Search as M, Send as N, Wallet as P } from "lucide-react";
6
- import { Fragment as F, jsx as I, jsxs as L } from "react/jsx-runtime";
7
- import { Button as R } 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";
8
9
  //#region src/proserviceworld/pages/TimePage.tsx
9
- var z = [
10
+ var pe = [
10
11
  "DRAFT",
11
12
  "SUBMITTED",
12
13
  "APPROVED",
13
14
  "REJECTED",
14
15
  "BILLED"
15
- ], B = ({ billable: e }) => e ? /* @__PURE__ */ L("span", {
16
+ ], me = ({ billable: e }) => e ? /* @__PURE__ */ N("span", {
16
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",
17
- children: [/* @__PURE__ */ I("span", { className: "h-1.5 w-1.5 rounded-full bg-current opacity-70" }), "Billable"]
18
- }) : /* @__PURE__ */ L("span", {
18
+ children: [/* @__PURE__ */ M("span", { className: "h-1.5 w-1.5 rounded-full bg-current opacity-70" }), "Billable"]
19
+ }) : /* @__PURE__ */ N("span", {
19
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",
20
- children: [/* @__PURE__ */ I("span", { className: "h-1.5 w-1.5 rounded-full bg-current opacity-60" }), "Non-billable"]
21
- }), V = () => {
22
- let [t, n] = D(""), [r, l] = D(""), f = E(r), [y, F] = D(!1), [V, U] = D(null), { data: W, isLoading: G, error: K, refetch: q } = a({
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({
23
24
  status: t || void 0,
24
- search: f.trim() || void 0
25
- }), { emitFact: J } = T(), Y = i(), X = e(), Z = (W?.items ?? []).reduce((e, t) => e + t.minutes, 0), Q = (W?.items ?? []).filter((e) => e.billable).reduce((e, t) => e + t.minutes, 0), te = Z > 0 ? Math.round(Q / Z * 100) : 0, $ = (e, t, n) => e.mutate(t, {
26
- onSuccess: () => U({
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({
27
28
  tone: "success",
28
29
  text: `Time entry ${n}.`
29
30
  }),
30
- onError: (e) => U({
31
+ onError: (e) => B({
31
32
  tone: "danger",
32
33
  text: e instanceof Error ? e.message : "Failed."
33
34
  })
34
35
  });
35
- return /* @__PURE__ */ L("div", {
36
+ return /* @__PURE__ */ N("div", {
36
37
  className: "space-y-6",
37
38
  children: [
38
- /* @__PURE__ */ I(m, {
39
+ /* @__PURE__ */ M(ee, {
39
40
  title: "Time",
40
41
  subtitle: "Capture expert effort against engagements and matters — the raw material of billing and utilization.",
41
- icon: /* @__PURE__ */ I(k, { className: "h-6 w-6" }),
42
- actions: /* @__PURE__ */ L(R, {
43
- onClick: () => F(!0),
44
- children: [/* @__PURE__ */ I(j, { className: "mr-1.5 h-4 w-4" }), "Log time"]
45
- })
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
+ })] })
46
58
  }),
47
- V && /* @__PURE__ */ I(o, {
48
- tone: V.tone,
49
- onClose: () => U(null),
50
- children: V.text
59
+ z && /* @__PURE__ */ M(c, {
60
+ tone: z.tone,
61
+ onClose: () => B(null),
62
+ children: z.text
51
63
  }),
52
- /* @__PURE__ */ L("div", {
64
+ /* @__PURE__ */ N("div", {
53
65
  className: "grid grid-cols-2 gap-3 sm:grid-cols-4",
54
66
  children: [
55
- /* @__PURE__ */ I(d, {
67
+ /* @__PURE__ */ M(p, {
56
68
  label: "Entries",
57
- value: W?.totalCount ?? 0,
58
- icon: /* @__PURE__ */ I(A, { className: "h-4 w-4" })
69
+ value: V?.totalCount ?? 0,
70
+ icon: /* @__PURE__ */ M(ue, { className: "h-4 w-4" })
59
71
  }),
60
- /* @__PURE__ */ I(d, {
72
+ /* @__PURE__ */ M(p, {
61
73
  label: "Total hours",
62
- value: w(Z),
63
- icon: /* @__PURE__ */ I(k, { className: "h-4 w-4" })
74
+ value: b(Z),
75
+ icon: /* @__PURE__ */ M(T, { className: "h-4 w-4" })
64
76
  }),
65
- /* @__PURE__ */ I(d, {
77
+ /* @__PURE__ */ M(p, {
66
78
  label: "Billable hours",
67
- value: w(Q),
68
- hint: `${te}% utilization`,
69
- icon: /* @__PURE__ */ I(P, { className: "h-4 w-4" }),
79
+ value: b(Q),
80
+ hint: `${xe}% utilization`,
81
+ icon: /* @__PURE__ */ M(A, { className: "h-4 w-4" }),
70
82
  tone: "accent"
71
83
  }),
72
- /* @__PURE__ */ I(d, {
84
+ /* @__PURE__ */ M(p, {
73
85
  label: "Non-billable",
74
- value: w(Z - Q),
75
- icon: /* @__PURE__ */ I(ee, { className: "h-4 w-4" })
86
+ value: b(Z - Q),
87
+ icon: /* @__PURE__ */ M(le, { className: "h-4 w-4" })
76
88
  })
77
89
  ]
78
90
  }),
79
- /* @__PURE__ */ L("div", {
91
+ /* @__PURE__ */ N("div", {
80
92
  className: "flex flex-col gap-2 sm:flex-row sm:items-center",
81
- children: [/* @__PURE__ */ L("div", {
93
+ children: [/* @__PURE__ */ N("div", {
82
94
  className: "relative flex-1",
83
- children: [/* @__PURE__ */ I(M, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary" }), /* @__PURE__ */ I(b, {
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, {
84
96
  placeholder: "Search time entries…",
85
97
  value: r,
86
- onChange: (e) => l(e.target.value),
98
+ onChange: (e) => d(e.target.value),
87
99
  onKeyDown: (e) => {
88
- e.key === "Enter" && r.trim() && J("psw.search.performed", {
100
+ e.key === "Enter" && r.trim() && ye("psw.search.performed", {
89
101
  entityType: "time-entry",
90
102
  queryLength: r.trim().length
91
103
  });
92
104
  },
93
105
  className: "pl-9"
94
106
  })]
95
- }), /* @__PURE__ */ L(p, {
107
+ }), /* @__PURE__ */ N(h, {
96
108
  value: t,
97
109
  onChange: (e) => n(e.target.value),
98
110
  className: "sm:w-52",
99
- children: [/* @__PURE__ */ I("option", {
111
+ children: [/* @__PURE__ */ M("option", {
100
112
  value: "",
101
113
  children: "All statuses"
102
- }), S(z)]
114
+ }), ie(pe)]
103
115
  })]
104
116
  }),
105
- G ? /* @__PURE__ */ I(u, {}) : K ? /* @__PURE__ */ I(c, {
106
- error: K,
107
- onRetry: () => void q()
108
- }) : !W || W.items.length === 0 ? /* @__PURE__ */ I(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, {
109
121
  title: "No time logged",
110
122
  description: "Log your first time entry against an engagement or matter.",
111
- icon: /* @__PURE__ */ I(k, { className: "h-8 w-8" }),
112
- action: /* @__PURE__ */ L(R, {
113
- onClick: () => F(!0),
114
- children: [/* @__PURE__ */ I(j, { 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"]
115
127
  })
116
- }) : /* @__PURE__ */ I(h, {
128
+ }) : /* @__PURE__ */ M(te, {
117
129
  title: "Time entries",
118
- children: /* @__PURE__ */ L(_, { children: [/* @__PURE__ */ I("thead", { children: /* @__PURE__ */ L("tr", { children: [
119
- /* @__PURE__ */ I(x, { children: "Date" }),
120
- /* @__PURE__ */ I(x, { children: "Narrative" }),
121
- /* @__PURE__ */ I(x, { children: "Hours" }),
122
- /* @__PURE__ */ I(x, { children: "Billable" }),
123
- /* @__PURE__ */ I(x, { children: "Status" }),
124
- /* @__PURE__ */ I(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, {
125
137
  className: "text-right",
126
138
  children: "Actions"
127
139
  })
128
- ] }) }), /* @__PURE__ */ I("tbody", { children: W.items.map((e) => /* @__PURE__ */ L("tr", {
140
+ ] }) }), /* @__PURE__ */ M("tbody", { children: V.items.map((e) => /* @__PURE__ */ N("tr", {
129
141
  className: "transition-colors hover:bg-bg-sunken/60",
130
142
  children: [
131
- /* @__PURE__ */ I(v, {
143
+ /* @__PURE__ */ M(g, {
132
144
  className: "whitespace-nowrap",
133
- children: C(e.date)
145
+ children: ae(e.date)
134
146
  }),
135
- /* @__PURE__ */ I(v, {
147
+ /* @__PURE__ */ M(g, {
136
148
  className: "max-w-xs truncate text-text-primary",
137
149
  children: e.narrative || "—"
138
150
  }),
139
- /* @__PURE__ */ I(v, {
151
+ /* @__PURE__ */ M(g, {
140
152
  className: "whitespace-nowrap font-medium text-text-primary",
141
- children: w(e.minutes)
153
+ children: b(e.minutes)
142
154
  }),
143
- /* @__PURE__ */ I(v, { children: /* @__PURE__ */ I(B, { billable: e.billable }) }),
144
- /* @__PURE__ */ I(v, { children: /* @__PURE__ */ I(g, { status: e.status }) }),
145
- /* @__PURE__ */ I(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, {
146
158
  className: "text-right",
147
- children: /* @__PURE__ */ L("div", {
159
+ children: /* @__PURE__ */ N("div", {
148
160
  className: "flex justify-end gap-1",
149
- children: [e.status === "DRAFT" && /* @__PURE__ */ L(R, {
150
- variant: "ghost",
151
- size: "sm",
152
- disabled: Y.isPending,
153
- onClick: () => $(Y, e.id, "submitted"),
154
- children: [/* @__PURE__ */ I(N, { className: "mr-1 h-3.5 w-3.5" }), "Submit"]
155
- }), e.status === "SUBMITTED" && /* @__PURE__ */ L(R, {
156
- variant: "ghost",
157
- size: "sm",
158
- disabled: X.isPending,
159
- onClick: () => $(X, e.id, "approved"),
160
- children: [/* @__PURE__ */ I(O, { className: "mr-1 h-3.5 w-3.5" }), "Approve"]
161
- })]
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
+ ]
162
194
  })
163
195
  })
164
196
  ]
165
197
  }, e.id)) })] })
166
198
  }),
167
- y && /* @__PURE__ */ I(H, {
168
- onClose: () => F(!1),
169
- onDone: () => U({
170
- tone: "success",
171
- text: "Time logged."
172
- })
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
+ }
173
227
  })
174
228
  ]
175
229
  });
176
- }, H = ({ onClose: e, onDone: i }) => {
177
- let a = n(), s = t({ pagination: { limit: 100 } }), c = r({ pagination: { limit: 100 } }), [u, d] = D({
178
- against: "engagement",
179
- engagementId: "",
180
- matterId: "",
181
- date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
182
- hours: "",
183
- minutes: "",
184
- rate: "",
185
- billable: !0,
186
- narrative: ""
187
- }), [m, h] = D(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) => ({
188
461
  ...n,
189
462
  [e]: t.target.value
190
463
  }));
191
- return /* @__PURE__ */ I(f, {
464
+ return /* @__PURE__ */ M(m, {
192
465
  open: !0,
193
466
  onClose: e,
194
- title: "Log time",
195
- footer: /* @__PURE__ */ L(F, { children: [/* @__PURE__ */ I(R, {
467
+ title: "Start timer",
468
+ footer: /* @__PURE__ */ N(j, { children: [/* @__PURE__ */ M(P, {
196
469
  variant: "outline",
197
470
  onClick: e,
198
- disabled: a.isPending,
199
471
  children: "Cancel"
200
- }), /* @__PURE__ */ I(R, {
472
+ }), /* @__PURE__ */ N(P, {
473
+ onClick: () => {
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, {
201
546
  onClick: () => {
202
- h(null);
203
- let t = (Number(u.hours) || 0) * 60 + (Number(u.minutes) || 0);
204
- if (t <= 0) return h("Enter the time spent.");
205
- let n = u.against === "engagement" ? { engagementId: u.engagementId } : { matterId: u.matterId };
206
- if (u.against === "engagement" && !u.engagementId) return h("Select an engagement.");
207
- if (u.against === "matter" && !u.matterId) return h("Select a matter.");
208
- a.mutate({
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({
209
554
  ...n,
210
- date: new Date(u.date).toISOString(),
211
- minutes: t,
212
- rate: u.rate ? Number(u.rate) : 0,
213
- billable: u.billable,
214
- 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
215
560
  }, {
216
561
  onSuccess: () => {
217
- i(), e();
562
+ r(), t();
218
563
  },
219
- onError: (e) => h(e instanceof Error ? e.message : "Failed.")
564
+ onError: (e) => l(e instanceof Error ? e.message : "Failed.")
220
565
  });
221
566
  },
222
- disabled: a.isPending,
223
- children: a.isPending ? "Logging…" : "Log time"
567
+ disabled: i.isPending,
568
+ children: i.isPending ? "Logging…" : "Log time"
224
569
  })] }),
225
- children: /* @__PURE__ */ L("div", {
570
+ children: /* @__PURE__ */ N("div", {
226
571
  className: "space-y-3",
227
572
  children: [
228
- m && /* @__PURE__ */ I(o, {
573
+ s && /* @__PURE__ */ M(c, {
229
574
  tone: "danger",
230
- children: m
575
+ children: s
231
576
  }),
232
- /* @__PURE__ */ I(l, {
577
+ /* @__PURE__ */ M(d, {
233
578
  label: "Against",
234
- children: /* @__PURE__ */ L(p, {
235
- value: u.against,
236
- onChange: g("against"),
237
- children: [/* @__PURE__ */ I("option", {
579
+ children: /* @__PURE__ */ N(h, {
580
+ value: a.against,
581
+ onChange: u("against"),
582
+ children: [/* @__PURE__ */ M("option", {
238
583
  value: "engagement",
239
584
  children: "Engagement"
240
- }), /* @__PURE__ */ I("option", {
585
+ }), /* @__PURE__ */ M("option", {
241
586
  value: "matter",
242
587
  children: "Matter"
243
588
  })]
244
589
  })
245
590
  }),
246
- u.against === "engagement" ? /* @__PURE__ */ I(l, {
247
- label: "Engagement",
248
- required: !0,
249
- children: /* @__PURE__ */ L(p, {
250
- value: u.engagementId,
251
- onChange: g("engagementId"),
252
- children: [/* @__PURE__ */ I("option", {
253
- value: "",
254
- children: "Select an engagement…"
255
- }), s.data?.items.map((e) => /* @__PURE__ */ I("option", {
256
- value: e.id,
257
- children: e.name
258
- }, e.id))]
259
- })
260
- }) : /* @__PURE__ */ I(l, {
261
- label: "Matter",
262
- required: !0,
263
- children: /* @__PURE__ */ L(p, {
264
- value: u.matterId,
265
- onChange: g("matterId"),
266
- children: [/* @__PURE__ */ I("option", {
267
- value: "",
268
- children: "Select a matter…"
269
- }), c.data?.items.map((e) => /* @__PURE__ */ I("option", {
270
- value: e.id,
271
- children: e.title
272
- }, e.id))]
273
- })
591
+ /* @__PURE__ */ M(V, {
592
+ form: a,
593
+ set: u
274
594
  }),
275
- /* @__PURE__ */ L("div", {
595
+ /* @__PURE__ */ N("div", {
276
596
  className: "grid grid-cols-3 gap-3",
277
597
  children: [
278
- /* @__PURE__ */ I(l, {
598
+ /* @__PURE__ */ M(d, {
279
599
  label: "Date",
280
- children: /* @__PURE__ */ I(b, {
600
+ children: /* @__PURE__ */ M(v, {
281
601
  type: "date",
282
- value: u.date,
283
- onChange: g("date")
602
+ value: a.date,
603
+ onChange: u("date")
284
604
  })
285
605
  }),
286
- /* @__PURE__ */ I(l, {
606
+ /* @__PURE__ */ M(d, {
287
607
  label: "Hours",
288
- children: /* @__PURE__ */ I(b, {
608
+ children: /* @__PURE__ */ M(v, {
289
609
  type: "number",
290
610
  min: "0",
291
- value: u.hours,
292
- onChange: g("hours"),
611
+ value: a.hours,
612
+ onChange: u("hours"),
293
613
  placeholder: "2"
294
614
  })
295
615
  }),
296
- /* @__PURE__ */ I(l, {
616
+ /* @__PURE__ */ M(d, {
297
617
  label: "Minutes",
298
- children: /* @__PURE__ */ I(b, {
618
+ children: /* @__PURE__ */ M(v, {
299
619
  type: "number",
300
620
  min: "0",
301
621
  max: "59",
302
- value: u.minutes,
303
- onChange: g("minutes"),
622
+ value: a.minutes,
623
+ onChange: u("minutes"),
304
624
  placeholder: "30"
305
625
  })
306
626
  })
307
627
  ]
308
628
  }),
309
- /* @__PURE__ */ I(l, {
629
+ /* @__PURE__ */ M(d, {
310
630
  label: "Rate (per hour)",
311
- children: /* @__PURE__ */ I(b, {
631
+ children: /* @__PURE__ */ M(v, {
312
632
  type: "number",
313
633
  min: "0",
314
- value: u.rate,
315
- onChange: g("rate"),
634
+ value: a.rate,
635
+ onChange: u("rate"),
316
636
  placeholder: "450"
317
637
  })
318
638
  }),
319
- /* @__PURE__ */ L("label", {
639
+ /* @__PURE__ */ N("label", {
320
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",
321
- children: [/* @__PURE__ */ I("input", {
641
+ children: [/* @__PURE__ */ M("input", {
322
642
  type: "checkbox",
323
- checked: u.billable,
324
- onChange: (e) => d((t) => ({
643
+ checked: a.billable,
644
+ onChange: (e) => o((t) => ({
325
645
  ...t,
326
646
  billable: e.target.checked
327
647
  }))
328
- }), /* @__PURE__ */ L("span", {
648
+ }), /* @__PURE__ */ N("span", {
329
649
  className: "flex items-center gap-1.5",
330
- children: [/* @__PURE__ */ I(P, { 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"]
331
651
  })]
332
652
  }),
333
- /* @__PURE__ */ I(l, {
653
+ /* @__PURE__ */ M(d, {
334
654
  label: "Narrative",
335
- children: /* @__PURE__ */ I(y, {
336
- value: u.narrative,
337
- onChange: g("narrative"),
655
+ children: /* @__PURE__ */ M(_, {
656
+ value: a.narrative,
657
+ onChange: u("narrative"),
338
658
  placeholder: "Reviewed working papers and drafted findings memo…"
339
659
  })
340
660
  })
@@ -343,6 +663,6 @@ var z = [
343
663
  });
344
664
  };
345
665
  //#endregion
346
- export { V as TimePage };
666
+ export { F as TimePage, B as timerToLogForm };
347
667
 
348
668
  //# sourceMappingURL=TimePage.js.map