@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.
- package/dist/proserviceworld/hooks/useProServiceWorldApi.d.ts +18 -0
- package/dist/proserviceworld/hooks/useProServiceWorldApi.d.ts.map +1 -1
- package/dist/proserviceworld/hooks/useProServiceWorldApi.js +67 -35
- package/dist/proserviceworld/hooks/useProServiceWorldApi.js.map +1 -1
- package/dist/proserviceworld/hooks/useTimeTimer.d.ts +24 -0
- package/dist/proserviceworld/hooks/useTimeTimer.d.ts.map +1 -0
- package/dist/proserviceworld/hooks/useTimeTimer.js +58 -0
- package/dist/proserviceworld/hooks/useTimeTimer.js.map +1 -0
- package/dist/proserviceworld/pages/InvoicesPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/InvoicesPage.js +308 -154
- package/dist/proserviceworld/pages/InvoicesPage.js.map +1 -1
- package/dist/proserviceworld/pages/TimePage.d.ts +7 -0
- package/dist/proserviceworld/pages/TimePage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/TimePage.js +514 -194
- package/dist/proserviceworld/pages/TimePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,340 +1,660 @@
|
|
|
1
|
-
import { useApproveTimeEntry as e, useEngagements as t, useLogTimeEntry as n, useMatters as r,
|
|
2
|
-
import { Banner as
|
|
3
|
-
import { useAnalytics as
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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
|
|
10
|
+
var pe = [
|
|
10
11
|
"DRAFT",
|
|
11
12
|
"SUBMITTED",
|
|
12
13
|
"APPROVED",
|
|
13
14
|
"REJECTED",
|
|
14
15
|
"BILLED"
|
|
15
|
-
],
|
|
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__ */
|
|
18
|
-
}) : /* @__PURE__ */
|
|
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__ */
|
|
21
|
-
}),
|
|
22
|
-
let [t, n] =
|
|
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:
|
|
25
|
-
}), { emitFact:
|
|
26
|
-
onSuccess: () =>
|
|
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) =>
|
|
31
|
+
onError: (e) => B({
|
|
31
32
|
tone: "danger",
|
|
32
33
|
text: e instanceof Error ? e.message : "Failed."
|
|
33
34
|
})
|
|
34
35
|
});
|
|
35
|
-
return /* @__PURE__ */
|
|
36
|
+
return /* @__PURE__ */ N("div", {
|
|
36
37
|
className: "space-y-6",
|
|
37
38
|
children: [
|
|
38
|
-
/* @__PURE__ */
|
|
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__ */
|
|
42
|
-
actions: /* @__PURE__ */
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
48
|
-
tone:
|
|
49
|
-
onClose: () =>
|
|
50
|
-
children:
|
|
59
|
+
z && /* @__PURE__ */ M(c, {
|
|
60
|
+
tone: z.tone,
|
|
61
|
+
onClose: () => B(null),
|
|
62
|
+
children: z.text
|
|
51
63
|
}),
|
|
52
|
-
/* @__PURE__ */
|
|
64
|
+
/* @__PURE__ */ N("div", {
|
|
53
65
|
className: "grid grid-cols-2 gap-3 sm:grid-cols-4",
|
|
54
66
|
children: [
|
|
55
|
-
/* @__PURE__ */
|
|
67
|
+
/* @__PURE__ */ M(p, {
|
|
56
68
|
label: "Entries",
|
|
57
|
-
value:
|
|
58
|
-
icon: /* @__PURE__ */
|
|
69
|
+
value: V?.totalCount ?? 0,
|
|
70
|
+
icon: /* @__PURE__ */ M(ue, { className: "h-4 w-4" })
|
|
59
71
|
}),
|
|
60
|
-
/* @__PURE__ */
|
|
72
|
+
/* @__PURE__ */ M(p, {
|
|
61
73
|
label: "Total hours",
|
|
62
|
-
value:
|
|
63
|
-
icon: /* @__PURE__ */
|
|
74
|
+
value: b(Z),
|
|
75
|
+
icon: /* @__PURE__ */ M(T, { className: "h-4 w-4" })
|
|
64
76
|
}),
|
|
65
|
-
/* @__PURE__ */
|
|
77
|
+
/* @__PURE__ */ M(p, {
|
|
66
78
|
label: "Billable hours",
|
|
67
|
-
value:
|
|
68
|
-
hint: `${
|
|
69
|
-
icon: /* @__PURE__ */
|
|
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__ */
|
|
84
|
+
/* @__PURE__ */ M(p, {
|
|
73
85
|
label: "Non-billable",
|
|
74
|
-
value:
|
|
75
|
-
icon: /* @__PURE__ */
|
|
86
|
+
value: b(Z - Q),
|
|
87
|
+
icon: /* @__PURE__ */ M(le, { className: "h-4 w-4" })
|
|
76
88
|
})
|
|
77
89
|
]
|
|
78
90
|
}),
|
|
79
|
-
/* @__PURE__ */
|
|
91
|
+
/* @__PURE__ */ N("div", {
|
|
80
92
|
className: "flex flex-col gap-2 sm:flex-row sm:items-center",
|
|
81
|
-
children: [/* @__PURE__ */
|
|
93
|
+
children: [/* @__PURE__ */ N("div", {
|
|
82
94
|
className: "relative flex-1",
|
|
83
|
-
children: [/* @__PURE__ */
|
|
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) =>
|
|
98
|
+
onChange: (e) => d(e.target.value),
|
|
87
99
|
onKeyDown: (e) => {
|
|
88
|
-
e.key === "Enter" && r.trim() &&
|
|
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__ */
|
|
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__ */
|
|
111
|
+
children: [/* @__PURE__ */ M("option", {
|
|
100
112
|
value: "",
|
|
101
113
|
children: "All statuses"
|
|
102
|
-
}),
|
|
114
|
+
}), ie(pe)]
|
|
103
115
|
})]
|
|
104
116
|
}),
|
|
105
|
-
|
|
106
|
-
error:
|
|
107
|
-
onRetry: () => void
|
|
108
|
-
}) : !
|
|
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__ */
|
|
112
|
-
action: /* @__PURE__ */
|
|
113
|
-
onClick: () =>
|
|
114
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
128
|
+
}) : /* @__PURE__ */ M(te, {
|
|
117
129
|
title: "Time entries",
|
|
118
|
-
children: /* @__PURE__ */
|
|
119
|
-
/* @__PURE__ */
|
|
120
|
-
/* @__PURE__ */
|
|
121
|
-
/* @__PURE__ */
|
|
122
|
-
/* @__PURE__ */
|
|
123
|
-
/* @__PURE__ */
|
|
124
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
143
|
+
/* @__PURE__ */ M(g, {
|
|
132
144
|
className: "whitespace-nowrap",
|
|
133
|
-
children:
|
|
145
|
+
children: ae(e.date)
|
|
134
146
|
}),
|
|
135
|
-
/* @__PURE__ */
|
|
147
|
+
/* @__PURE__ */ M(g, {
|
|
136
148
|
className: "max-w-xs truncate text-text-primary",
|
|
137
149
|
children: e.narrative || "—"
|
|
138
150
|
}),
|
|
139
|
-
/* @__PURE__ */
|
|
151
|
+
/* @__PURE__ */ M(g, {
|
|
140
152
|
className: "whitespace-nowrap font-medium text-text-primary",
|
|
141
|
-
children:
|
|
153
|
+
children: b(e.minutes)
|
|
142
154
|
}),
|
|
143
|
-
/* @__PURE__ */
|
|
144
|
-
/* @__PURE__ */
|
|
145
|
-
/* @__PURE__ */
|
|
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__ */
|
|
159
|
+
children: /* @__PURE__ */ N("div", {
|
|
148
160
|
className: "flex justify-end gap-1",
|
|
149
|
-
children: [
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
},
|
|
177
|
-
let
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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__ */
|
|
464
|
+
return /* @__PURE__ */ M(m, {
|
|
192
465
|
open: !0,
|
|
193
466
|
onClose: e,
|
|
194
|
-
title: "
|
|
195
|
-
footer: /* @__PURE__ */
|
|
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__ */
|
|
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
|
-
|
|
203
|
-
let
|
|
204
|
-
if (
|
|
205
|
-
let n =
|
|
206
|
-
if (
|
|
207
|
-
if (
|
|
208
|
-
|
|
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(
|
|
211
|
-
minutes:
|
|
212
|
-
rate:
|
|
213
|
-
billable:
|
|
214
|
-
narrative:
|
|
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
|
-
|
|
562
|
+
r(), t();
|
|
218
563
|
},
|
|
219
|
-
onError: (e) =>
|
|
564
|
+
onError: (e) => l(e instanceof Error ? e.message : "Failed.")
|
|
220
565
|
});
|
|
221
566
|
},
|
|
222
|
-
disabled:
|
|
223
|
-
children:
|
|
567
|
+
disabled: i.isPending,
|
|
568
|
+
children: i.isPending ? "Logging…" : "Log time"
|
|
224
569
|
})] }),
|
|
225
|
-
children: /* @__PURE__ */
|
|
570
|
+
children: /* @__PURE__ */ N("div", {
|
|
226
571
|
className: "space-y-3",
|
|
227
572
|
children: [
|
|
228
|
-
|
|
573
|
+
s && /* @__PURE__ */ M(c, {
|
|
229
574
|
tone: "danger",
|
|
230
|
-
children:
|
|
575
|
+
children: s
|
|
231
576
|
}),
|
|
232
|
-
/* @__PURE__ */
|
|
577
|
+
/* @__PURE__ */ M(d, {
|
|
233
578
|
label: "Against",
|
|
234
|
-
children: /* @__PURE__ */
|
|
235
|
-
value:
|
|
236
|
-
onChange:
|
|
237
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
585
|
+
}), /* @__PURE__ */ M("option", {
|
|
241
586
|
value: "matter",
|
|
242
587
|
children: "Matter"
|
|
243
588
|
})]
|
|
244
589
|
})
|
|
245
590
|
}),
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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__ */
|
|
595
|
+
/* @__PURE__ */ N("div", {
|
|
276
596
|
className: "grid grid-cols-3 gap-3",
|
|
277
597
|
children: [
|
|
278
|
-
/* @__PURE__ */
|
|
598
|
+
/* @__PURE__ */ M(d, {
|
|
279
599
|
label: "Date",
|
|
280
|
-
children: /* @__PURE__ */
|
|
600
|
+
children: /* @__PURE__ */ M(v, {
|
|
281
601
|
type: "date",
|
|
282
|
-
value:
|
|
283
|
-
onChange:
|
|
602
|
+
value: a.date,
|
|
603
|
+
onChange: u("date")
|
|
284
604
|
})
|
|
285
605
|
}),
|
|
286
|
-
/* @__PURE__ */
|
|
606
|
+
/* @__PURE__ */ M(d, {
|
|
287
607
|
label: "Hours",
|
|
288
|
-
children: /* @__PURE__ */
|
|
608
|
+
children: /* @__PURE__ */ M(v, {
|
|
289
609
|
type: "number",
|
|
290
610
|
min: "0",
|
|
291
|
-
value:
|
|
292
|
-
onChange:
|
|
611
|
+
value: a.hours,
|
|
612
|
+
onChange: u("hours"),
|
|
293
613
|
placeholder: "2"
|
|
294
614
|
})
|
|
295
615
|
}),
|
|
296
|
-
/* @__PURE__ */
|
|
616
|
+
/* @__PURE__ */ M(d, {
|
|
297
617
|
label: "Minutes",
|
|
298
|
-
children: /* @__PURE__ */
|
|
618
|
+
children: /* @__PURE__ */ M(v, {
|
|
299
619
|
type: "number",
|
|
300
620
|
min: "0",
|
|
301
621
|
max: "59",
|
|
302
|
-
value:
|
|
303
|
-
onChange:
|
|
622
|
+
value: a.minutes,
|
|
623
|
+
onChange: u("minutes"),
|
|
304
624
|
placeholder: "30"
|
|
305
625
|
})
|
|
306
626
|
})
|
|
307
627
|
]
|
|
308
628
|
}),
|
|
309
|
-
/* @__PURE__ */
|
|
629
|
+
/* @__PURE__ */ M(d, {
|
|
310
630
|
label: "Rate (per hour)",
|
|
311
|
-
children: /* @__PURE__ */
|
|
631
|
+
children: /* @__PURE__ */ M(v, {
|
|
312
632
|
type: "number",
|
|
313
633
|
min: "0",
|
|
314
|
-
value:
|
|
315
|
-
onChange:
|
|
634
|
+
value: a.rate,
|
|
635
|
+
onChange: u("rate"),
|
|
316
636
|
placeholder: "450"
|
|
317
637
|
})
|
|
318
638
|
}),
|
|
319
|
-
/* @__PURE__ */
|
|
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__ */
|
|
641
|
+
children: [/* @__PURE__ */ M("input", {
|
|
322
642
|
type: "checkbox",
|
|
323
|
-
checked:
|
|
324
|
-
onChange: (e) =>
|
|
643
|
+
checked: a.billable,
|
|
644
|
+
onChange: (e) => o((t) => ({
|
|
325
645
|
...t,
|
|
326
646
|
billable: e.target.checked
|
|
327
647
|
}))
|
|
328
|
-
}), /* @__PURE__ */
|
|
648
|
+
}), /* @__PURE__ */ N("span", {
|
|
329
649
|
className: "flex items-center gap-1.5",
|
|
330
|
-
children: [/* @__PURE__ */
|
|
650
|
+
children: [/* @__PURE__ */ M(A, { className: "h-3.5 w-3.5 text-status-success-text" }), "Billable"]
|
|
331
651
|
})]
|
|
332
652
|
}),
|
|
333
|
-
/* @__PURE__ */
|
|
653
|
+
/* @__PURE__ */ M(d, {
|
|
334
654
|
label: "Narrative",
|
|
335
|
-
children: /* @__PURE__ */
|
|
336
|
-
value:
|
|
337
|
-
onChange:
|
|
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 {
|
|
666
|
+
export { F as TimePage, B as timerToLogForm };
|
|
347
667
|
|
|
348
668
|
//# sourceMappingURL=TimePage.js.map
|