@burdenoff/microfe-proserviceworld 2026.731.1 → 2026.827.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 (26) hide show
  1. package/dist/proserviceworld/ProServiceWorldRoutes.js +1 -1
  2. package/dist/proserviceworld/ProServiceWorldRoutes.js.map +1 -1
  3. package/dist/proserviceworld/hooks/useActorNames.d.ts +16 -0
  4. package/dist/proserviceworld/hooks/useActorNames.d.ts.map +1 -0
  5. package/dist/proserviceworld/hooks/useActorNames.js +18 -0
  6. package/dist/proserviceworld/hooks/useActorNames.js.map +1 -0
  7. package/dist/proserviceworld/hooks/usePageViewAnalytics.js +1 -1
  8. package/dist/proserviceworld/hooks/usePageViewAnalytics.js.map +1 -1
  9. package/dist/proserviceworld/hooks/useProServiceWorldApi.d.ts +53 -0
  10. package/dist/proserviceworld/hooks/useProServiceWorldApi.d.ts.map +1 -1
  11. package/dist/proserviceworld/hooks/useProServiceWorldApi.js +289 -227
  12. package/dist/proserviceworld/hooks/useProServiceWorldApi.js.map +1 -1
  13. package/dist/proserviceworld/pages/ClientDetailPage.d.ts.map +1 -1
  14. package/dist/proserviceworld/pages/ClientDetailPage.js +268 -144
  15. package/dist/proserviceworld/pages/ClientDetailPage.js.map +1 -1
  16. package/dist/proserviceworld/pages/ClientsPage.d.ts.map +1 -1
  17. package/dist/proserviceworld/pages/ClientsPage.js +244 -163
  18. package/dist/proserviceworld/pages/ClientsPage.js.map +1 -1
  19. package/dist/proserviceworld/pages/EngagementDetailPage.d.ts.map +1 -1
  20. package/dist/proserviceworld/pages/EngagementDetailPage.js +420 -186
  21. package/dist/proserviceworld/pages/EngagementDetailPage.js.map +1 -1
  22. package/dist/proserviceworld/providers/ProServiceWorldProvider.js +1 -1
  23. package/dist/proserviceworld/providers/ProServiceWorldProvider.js.map +1 -1
  24. package/dist/proserviceworld/utils/nav.js +1 -1
  25. package/dist/proserviceworld/utils/nav.js.map +1 -1
  26. package/package.json +4 -4
@@ -1,15 +1,18 @@
1
- import { useCompleteMilestone as e, useCreateDeliverable as t, useCreateMilestone as n, useEngagement as r, useTimeEntries as i, useTransitionDeliverable as a, useTransitionEngagement as o } from "../hooks/useProServiceWorldApi.js";
2
- import { useNav as s } from "../utils/nav.js";
3
- 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 g, SectionCard as _, StatusPill as v, TextArea as y, TextInput as b, fmtDate as x, fmtMinutesAsHours as S, fmtMoney as C, humanize as w } from "../components/ui.js";
4
- import { DiscussionThread as T } from "../components/DiscussionThread.js";
5
- import { useAnalytics as E } from "../hooks/useAnalytics.js";
6
- import { useState as D } from "react";
7
- import { ArrowLeft as O, ArrowRight as k, CheckCircle2 as A, Clock as j, DollarSign as M, FileText as N, Flag as P, Plus as F } from "lucide-react";
8
- import { useParams as I } from "react-router-dom";
9
- import { Fragment as L, jsx as R, jsxs as z } from "react/jsx-runtime";
10
- import { Button as B } from "@burdenoff/fe-libs/ui";
1
+ import { useProServiceWorld as e } from "../providers/ProServiceWorldProvider.js";
2
+ import { useAddTeamAssignment as t, useCompleteMilestone as n, useCreateDeliverable as r, useCreateMilestone as i, useEngagement as a, usePracticeAreas as o, useRemoveTeamAssignment as s, useTeamAssignments as c, useTimeEntries as l, useTransitionDeliverable as u, useTransitionEngagement as d } from "../hooks/useProServiceWorldApi.js";
3
+ import { useNav as f } from "../utils/nav.js";
4
+ import { Banner as p, Empty as m, ErrorState as h, Field as g, LoadingBlock as _, MetricCard as v, Modal as y, NativeSelect as b, PageHeader as x, SectionCard as S, StatusPill as C, TableWrap as w, Td as T, TextArea as E, TextInput as D, Th as O, enumOptions as k, fmtDate as A, fmtMinutesAsHours as j, fmtMoney as M, humanize as N } from "../components/ui.js";
5
+ import { useActorNames as ee } from "../hooks/useActorNames.js";
6
+ import { DiscussionThread as te } from "../components/DiscussionThread.js";
7
+ import { useAnalytics as ne } from "../hooks/useAnalytics.js";
8
+ import { useState as P } from "react";
9
+ import { ArrowLeft as re, ArrowRight as ie, CheckCircle2 as F, Clock as I, DollarSign as ae, FileText as L, Flag as R, Plus as z, Trash2 as B, Users as oe } from "lucide-react";
10
+ import { useParams as se } from "react-router";
11
+ import { Fragment as V, jsx as H, jsxs as U } from "react/jsx-runtime";
12
+ import { Button as W } from "@burdenoff/fe-libs/ui";
13
+ import { useWorkspaceDirectory as G } from "@burdenoff/fe-libs/sharing";
11
14
  //#region src/proserviceworld/pages/EngagementDetailPage.tsx
12
- var V = {
15
+ var ce = {
13
16
  PROPOSAL: ["ACCEPTED", "CANCELLED"],
14
17
  ACCEPTED: ["IN_PROGRESS", "CANCELLED"],
15
18
  IN_PROGRESS: ["REVIEW", "CANCELLED"],
@@ -22,233 +25,286 @@ var V = {
22
25
  INVOICED: ["CLOSED"],
23
26
  CLOSED: [],
24
27
  CANCELLED: []
25
- }, H = [
28
+ }, K = [
26
29
  "DRAFT",
27
30
  "INTERNAL_REVIEW",
28
31
  "CLIENT_REVIEW",
29
32
  "ACCEPTED",
30
33
  "REJECTED"
31
- ], U = () => {
32
- let { id: e = "" } = I(), t = s(), { emitFact: n } = E(), { data: a, isLoading: d, error: m, refetch: h } = r(e), y = i({ engagementId: e }), b = o(), [A, L] = D(!1), [H, U] = D(!1), [J, Y] = D(null);
33
- if (d) return /* @__PURE__ */ R(f, {});
34
- if (m) return /* @__PURE__ */ R(u, {
35
- error: m,
36
- onRetry: () => void h()
34
+ ], q = [
35
+ "PARTNER",
36
+ "MANAGER",
37
+ "ASSOCIATE",
38
+ "SPECIALIST",
39
+ "SUPPORT"
40
+ ], J = () => {
41
+ let { id: e = "" } = se(), t = f(), { emitFact: n } = ne(), { data: r, isLoading: i, error: s, refetch: u } = a(e), g = l({ engagementId: e }), y = o(), b = c({ engagementId: e }), T = d(), [E, D] = P(!1), [k, F] = P(!1), [B, V] = P(!1), [G, K] = P(null), q = ee([r?.responsiblePartnerId, ...(b.data?.items ?? []).map((e) => e.practitionerId)]);
42
+ if (i) return /* @__PURE__ */ H(_, {});
43
+ if (s) return /* @__PURE__ */ H(h, {
44
+ error: s,
45
+ onRetry: () => void u()
37
46
  });
38
- if (!a) return /* @__PURE__ */ R(l, { title: "Engagement not found" });
39
- let X = V[a.status] ?? [], Z = (y.data?.items ?? []).reduce((e, t) => e + t.minutes, 0);
40
- return /* @__PURE__ */ z("div", {
47
+ if (!r) return /* @__PURE__ */ H(m, { title: "Engagement not found" });
48
+ let J = ce[r.status] ?? [], Y = g.data?.items ?? [], X = Y.length, Z = g.data?.totalCount ?? X, Q = X < Z ? ` · first ${X} of ${Z} entries` : "", $ = Y.reduce((e, t) => e + t.minutes, 0), he = Y.filter((e) => e.billable && e.status !== "BILLED").reduce((e, t) => e + t.minutes, 0), ge = Y.filter((e) => e.status === "BILLED").reduce((e, t) => e + t.minutes, 0), _e = r.milestones.filter((e) => e.status === "COMPLETED").length, ve = r.deliverables.filter((e) => e.status === "CLIENT_REVIEW").length, ye = y.data?.items.find((e) => e.id === r.practiceAreaId)?.name ?? null, be = [
49
+ r.client?.name,
50
+ ye,
51
+ N(r.feeArrangement),
52
+ r.responsiblePartnerId ? `Partner: ${q.nameOf(r.responsiblePartnerId)}` : null
53
+ ].filter(Boolean).join(" · ");
54
+ return /* @__PURE__ */ U("div", {
41
55
  className: "space-y-6",
42
56
  children: [
43
- /* @__PURE__ */ z("button", {
57
+ /* @__PURE__ */ U("button", {
44
58
  type: "button",
45
59
  onClick: () => t("engagements"),
46
60
  className: "flex items-center gap-1.5 text-sm text-text-secondary transition-colors hover:text-accent",
47
- children: [/* @__PURE__ */ R(O, { className: "h-4 w-4" }), " Back to engagements"]
61
+ children: [/* @__PURE__ */ H(re, { className: "h-4 w-4" }), " Back to engagements"]
48
62
  }),
49
- /* @__PURE__ */ R(g, {
50
- title: a.name,
51
- subtitle: a.client ? `${a.client.name} · ${w(a.feeArrangement)}` : w(a.feeArrangement),
52
- actions: /* @__PURE__ */ R(v, { status: a.status })
63
+ /* @__PURE__ */ H(x, {
64
+ title: r.name,
65
+ subtitle: be,
66
+ actions: /* @__PURE__ */ H(C, { status: r.status })
53
67
  }),
54
- J && /* @__PURE__ */ R(c, {
55
- tone: J.tone,
56
- onClose: () => Y(null),
57
- children: J.text
68
+ G && /* @__PURE__ */ H(p, {
69
+ tone: G.tone,
70
+ onClose: () => K(null),
71
+ children: G.text
58
72
  }),
59
- /* @__PURE__ */ z("div", {
73
+ /* @__PURE__ */ U("div", {
60
74
  className: "grid grid-cols-2 gap-3 lg:grid-cols-4",
61
75
  children: [
62
- /* @__PURE__ */ R(p, {
76
+ /* @__PURE__ */ H(v, {
63
77
  label: "Fee",
64
- value: C(a.feeAmount, a.currency),
65
- hint: w(a.feeArrangement),
66
- icon: /* @__PURE__ */ R(M, { className: "h-4 w-4" }),
78
+ value: M(r.feeAmount, r.currency),
79
+ hint: N(r.feeArrangement),
80
+ icon: /* @__PURE__ */ H(ae, { className: "h-4 w-4" }),
67
81
  tone: "success"
68
82
  }),
69
- /* @__PURE__ */ R(p, {
83
+ /* @__PURE__ */ H(v, {
70
84
  label: "Deliverables",
71
- value: a.deliverables.length,
72
- icon: /* @__PURE__ */ R(N, { className: "h-4 w-4" }),
85
+ value: r.deliverables.length,
86
+ hint: `${ve} in client review`,
87
+ icon: /* @__PURE__ */ H(L, { className: "h-4 w-4" }),
73
88
  tone: "info"
74
89
  }),
75
- /* @__PURE__ */ R(p, {
90
+ /* @__PURE__ */ H(v, {
76
91
  label: "Milestones",
77
- value: a.milestones.length,
78
- icon: /* @__PURE__ */ R(P, { className: "h-4 w-4" }),
92
+ value: `${_e} of ${r.milestones.length}`,
93
+ hint: "Completed",
94
+ icon: /* @__PURE__ */ H(R, { className: "h-4 w-4" }),
79
95
  tone: "accent"
80
96
  }),
81
- /* @__PURE__ */ R(p, {
82
- label: "Time logged",
83
- value: S(Z),
84
- hint: `${y.data?.totalCount ?? 0} entries`,
85
- icon: /* @__PURE__ */ R(j, { className: "h-4 w-4" }),
97
+ /* @__PURE__ */ H(v, {
98
+ label: "Unbilled hours",
99
+ value: j(he),
100
+ hint: `${j(ge)} billed · ${j($)} logged${Q}`,
101
+ icon: /* @__PURE__ */ H(I, { className: "h-4 w-4" }),
86
102
  tone: "warning"
87
103
  })
88
104
  ]
89
105
  }),
90
- X.length > 0 && /* @__PURE__ */ R(_, {
106
+ J.length > 0 && /* @__PURE__ */ H(S, {
91
107
  title: "Advance lifecycle",
92
- children: /* @__PURE__ */ z("div", {
108
+ children: /* @__PURE__ */ U("div", {
93
109
  className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
94
- children: [/* @__PURE__ */ z("div", {
110
+ children: [/* @__PURE__ */ U("div", {
95
111
  className: "flex items-center gap-2 text-sm text-text-secondary",
96
- children: [/* @__PURE__ */ R("span", { children: "Currently" }), /* @__PURE__ */ R(v, { status: a.status })]
97
- }), /* @__PURE__ */ R("div", {
112
+ children: [/* @__PURE__ */ H("span", { children: "Currently" }), /* @__PURE__ */ H(C, { status: r.status })]
113
+ }), /* @__PURE__ */ H("div", {
98
114
  className: "flex flex-wrap gap-2",
99
- children: X.map((e) => {
115
+ children: J.map((e) => {
100
116
  let t = e === "CANCELLED";
101
- return /* @__PURE__ */ z(B, {
117
+ return /* @__PURE__ */ U(W, {
102
118
  size: "sm",
103
119
  variant: t ? "outline" : "default",
104
- disabled: b.isPending,
105
- onClick: () => b.mutate({
106
- id: a.id,
120
+ disabled: T.isPending,
121
+ onClick: () => T.mutate({
122
+ id: r.id,
107
123
  status: e
108
124
  }, {
109
125
  onSuccess: () => {
110
126
  n("psw.engagement.transitioned", {
111
- engagementId: a.id,
127
+ engagementId: r.id,
112
128
  status: e
113
- }), Y({
129
+ }), K({
114
130
  tone: "success",
115
- text: `Moved to ${w(e)}.`
131
+ text: `Moved to ${N(e)}.`
116
132
  });
117
133
  },
118
- onError: (e) => Y({
134
+ onError: (e) => K({
119
135
  tone: "danger",
120
136
  text: e instanceof Error ? e.message : "Transition failed."
121
137
  })
122
138
  }),
123
- children: [w(e), !t && /* @__PURE__ */ R(k, { className: "ml-1.5 h-4 w-4" })]
139
+ children: [N(e), !t && /* @__PURE__ */ H(ie, { className: "ml-1.5 h-4 w-4" })]
124
140
  }, e);
125
141
  })
126
142
  })]
127
143
  })
128
144
  }),
129
- /* @__PURE__ */ z("div", {
145
+ /* @__PURE__ */ U("div", {
130
146
  className: "grid grid-cols-1 gap-4 lg:grid-cols-2",
131
- children: [/* @__PURE__ */ R(_, {
147
+ children: [/* @__PURE__ */ H(S, {
132
148
  title: "Deliverables",
133
- action: /* @__PURE__ */ z(B, {
149
+ action: /* @__PURE__ */ U(W, {
134
150
  size: "sm",
135
151
  variant: "outline",
136
- onClick: () => L(!0),
137
- children: [/* @__PURE__ */ R(F, { className: "mr-1.5 h-4 w-4" }), "Add"]
152
+ onClick: () => D(!0),
153
+ children: [/* @__PURE__ */ H(z, { className: "mr-1.5 h-4 w-4" }), "Add"]
138
154
  }),
139
- children: a.deliverables.length === 0 ? /* @__PURE__ */ R(l, {
155
+ children: r.deliverables.length === 0 ? /* @__PURE__ */ H(m, {
140
156
  title: "No deliverables",
141
157
  description: "Track the tangible outputs of this engagement.",
142
- icon: /* @__PURE__ */ R(N, { className: "h-7 w-7" })
143
- }) : /* @__PURE__ */ R("ul", {
158
+ icon: /* @__PURE__ */ H(L, { className: "h-7 w-7" })
159
+ }) : /* @__PURE__ */ H("ul", {
144
160
  className: "space-y-2.5",
145
- children: a.deliverables.map((e) => /* @__PURE__ */ R("li", {
161
+ children: r.deliverables.map((e) => /* @__PURE__ */ H("li", {
146
162
  className: "rounded-xl border border-border-seam bg-bg-canvas p-3.5 transition-colors hover:border-accent/40",
147
- children: /* @__PURE__ */ z("div", {
163
+ children: /* @__PURE__ */ U("div", {
148
164
  className: "flex items-start justify-between gap-3",
149
- children: [/* @__PURE__ */ z("div", {
165
+ children: [/* @__PURE__ */ U("div", {
150
166
  className: "min-w-0",
151
- children: [/* @__PURE__ */ z("div", {
167
+ children: [/* @__PURE__ */ U("div", {
152
168
  className: "flex items-center gap-2 text-sm font-medium text-text-primary",
153
- children: [/* @__PURE__ */ R("span", {
169
+ children: [/* @__PURE__ */ H("span", {
154
170
  className: "truncate",
155
171
  children: e.name
156
- }), /* @__PURE__ */ z("span", {
172
+ }), /* @__PURE__ */ U("span", {
157
173
  className: "shrink-0 rounded-md bg-bg-sunken px-1.5 py-0.5 text-[10px] font-semibold text-text-tertiary",
158
174
  children: ["v", e.version]
159
175
  })]
160
- }), e.dueDate && /* @__PURE__ */ z("div", {
176
+ }), e.dueDate && /* @__PURE__ */ U("div", {
161
177
  className: "mt-0.5 text-xs text-text-tertiary",
162
- children: ["Due ", x(e.dueDate)]
178
+ children: ["Due ", A(e.dueDate)]
163
179
  })]
164
- }), /* @__PURE__ */ R(W, {
180
+ }), /* @__PURE__ */ H(de, {
165
181
  id: e.id,
166
- engagementId: a.id,
182
+ engagementId: r.id,
167
183
  status: e.status,
168
- onDone: () => void h()
184
+ onDone: () => void u()
169
185
  })]
170
186
  })
171
187
  }, e.id))
172
188
  })
173
- }), /* @__PURE__ */ R(_, {
189
+ }), /* @__PURE__ */ H(S, {
174
190
  title: "Milestones",
175
- action: /* @__PURE__ */ z(B, {
191
+ action: /* @__PURE__ */ U(W, {
176
192
  size: "sm",
177
193
  variant: "outline",
178
- onClick: () => U(!0),
179
- children: [/* @__PURE__ */ R(F, { className: "mr-1.5 h-4 w-4" }), "Add"]
194
+ onClick: () => F(!0),
195
+ children: [/* @__PURE__ */ H(z, { className: "mr-1.5 h-4 w-4" }), "Add"]
180
196
  }),
181
- children: a.milestones.length === 0 ? /* @__PURE__ */ R(l, {
197
+ children: r.milestones.length === 0 ? /* @__PURE__ */ H(m, {
182
198
  title: "No milestones",
183
199
  description: "Add checkpoints — some can trigger billing.",
184
- icon: /* @__PURE__ */ R(P, { className: "h-7 w-7" })
185
- }) : /* @__PURE__ */ R("ul", {
200
+ icon: /* @__PURE__ */ H(R, { className: "h-7 w-7" })
201
+ }) : /* @__PURE__ */ H("ul", {
186
202
  className: "space-y-2.5",
187
- children: a.milestones.map((e) => /* @__PURE__ */ z("li", {
203
+ children: r.milestones.map((e) => /* @__PURE__ */ U("li", {
188
204
  className: "flex items-center justify-between gap-3 rounded-xl border border-border-seam bg-bg-canvas p-3.5 transition-colors hover:border-accent/40",
189
- children: [/* @__PURE__ */ z("div", {
205
+ children: [/* @__PURE__ */ U("div", {
190
206
  className: "min-w-0",
191
- children: [/* @__PURE__ */ R("div", {
207
+ children: [/* @__PURE__ */ H("div", {
192
208
  className: "truncate text-sm font-medium text-text-primary",
193
209
  children: e.name
194
- }), /* @__PURE__ */ z("div", {
210
+ }), /* @__PURE__ */ U("div", {
195
211
  className: "mt-0.5 text-xs text-text-tertiary",
196
212
  children: [
197
- e.triggersInvoice ? `Bills ${C(e.triggerAmount, a.currency)} · ` : "",
213
+ e.triggersInvoice ? `Bills ${M(e.triggerAmount, r.currency)} · ` : "",
198
214
  "Due ",
199
- x(e.dueDate)
215
+ A(e.dueDate)
200
216
  ]
201
217
  })]
202
- }), /* @__PURE__ */ z("div", {
218
+ }), /* @__PURE__ */ U("div", {
203
219
  className: "flex shrink-0 items-center gap-2",
204
- children: [/* @__PURE__ */ R(v, { status: e.status }), e.status !== "COMPLETED" && e.status !== "CANCELLED" && /* @__PURE__ */ R(G, {
220
+ children: [/* @__PURE__ */ H(C, { status: e.status }), e.status !== "COMPLETED" && e.status !== "CANCELLED" && /* @__PURE__ */ H(fe, {
205
221
  id: e.id,
206
- engagementId: a.id,
207
- onDone: () => void h()
222
+ engagementId: r.id,
223
+ onDone: () => void u()
208
224
  })]
209
225
  })]
210
226
  }, e.id))
211
227
  })
212
228
  })]
213
229
  }),
214
- /* @__PURE__ */ z("div", {
230
+ /* @__PURE__ */ H(S, {
231
+ title: "Team",
232
+ description: `${b.data?.totalCount ?? 0} staffed on this engagement`,
233
+ action: /* @__PURE__ */ U(W, {
234
+ size: "sm",
235
+ variant: "outline",
236
+ onClick: () => V(!0),
237
+ children: [/* @__PURE__ */ H(z, { className: "mr-1.5 h-4 w-4" }), "Assign"]
238
+ }),
239
+ children: b.isLoading ? /* @__PURE__ */ H(_, { label: "Loading…" }) : b.error ? /* @__PURE__ */ H(h, {
240
+ error: b.error,
241
+ onRetry: () => void b.refetch()
242
+ }) : !b.data || b.data.items.length === 0 ? /* @__PURE__ */ H(m, {
243
+ title: "No one assigned",
244
+ description: "Staff the practitioners delivering this engagement, with their role and billing rate.",
245
+ icon: /* @__PURE__ */ H(oe, { className: "h-7 w-7" })
246
+ }) : /* @__PURE__ */ U(w, { children: [/* @__PURE__ */ H("thead", { children: /* @__PURE__ */ U("tr", { children: [
247
+ /* @__PURE__ */ H(O, { children: "Practitioner" }),
248
+ /* @__PURE__ */ H(O, { children: "Role" }),
249
+ /* @__PURE__ */ H(O, { children: "Allocation" }),
250
+ /* @__PURE__ */ H(O, { children: "Billing rate" }),
251
+ /* @__PURE__ */ H(O, {
252
+ className: "text-right",
253
+ children: "Actions"
254
+ })
255
+ ] }) }), /* @__PURE__ */ H("tbody", { children: b.data.items.map((e) => /* @__PURE__ */ H(le, {
256
+ assignment: e,
257
+ currency: r.currency,
258
+ actors: q,
259
+ onRemoved: (e) => {
260
+ b.refetch(), K(e ? {
261
+ tone: "success",
262
+ text: "Team assignment removed."
263
+ } : {
264
+ tone: "danger",
265
+ text: "Could not remove the assignment."
266
+ });
267
+ }
268
+ }, e.id)) })] })
269
+ }),
270
+ /* @__PURE__ */ U("div", {
215
271
  className: "grid grid-cols-1 gap-4 lg:grid-cols-3",
216
- children: [/* @__PURE__ */ R("div", {
272
+ children: [/* @__PURE__ */ H("div", {
217
273
  className: "lg:col-span-2",
218
- children: /* @__PURE__ */ R(T, {
274
+ children: /* @__PURE__ */ H(te, {
219
275
  entityType: "proserviceworld-engagement",
220
- entityId: a.id
276
+ entityId: r.id
221
277
  })
222
- }), /* @__PURE__ */ R(_, {
278
+ }), /* @__PURE__ */ H(S, {
223
279
  title: "Time logged",
224
- description: S(Z),
225
- action: /* @__PURE__ */ z(B, {
280
+ description: `${j($)}${Q}`,
281
+ action: /* @__PURE__ */ U(W, {
226
282
  size: "sm",
227
283
  variant: "outline",
228
284
  onClick: () => t("time"),
229
- children: [/* @__PURE__ */ R(j, { className: "mr-1.5 h-4 w-4" }), "Log time"]
285
+ children: [/* @__PURE__ */ H(I, { className: "mr-1.5 h-4 w-4" }), "Log time"]
230
286
  }),
231
- children: y.isLoading ? /* @__PURE__ */ R(f, { label: "Loading…" }) : !y.data || y.data.items.length === 0 ? /* @__PURE__ */ R(l, {
287
+ children: g.isLoading ? /* @__PURE__ */ H(_, { label: "Loading…" }) : !g.data || g.data.items.length === 0 ? /* @__PURE__ */ H(m, {
232
288
  title: "No time logged",
233
289
  description: "Time logged against this engagement will appear here.",
234
- icon: /* @__PURE__ */ R(j, { className: "h-7 w-7" })
235
- }) : /* @__PURE__ */ R("ul", {
290
+ icon: /* @__PURE__ */ H(I, { className: "h-7 w-7" })
291
+ }) : /* @__PURE__ */ H("ul", {
236
292
  className: "space-y-2",
237
- children: y.data.items.slice(0, 8).map((e) => /* @__PURE__ */ z("li", {
293
+ children: g.data.items.slice(0, 8).map((e) => /* @__PURE__ */ U("li", {
238
294
  className: "rounded-lg border border-border-seam bg-bg-canvas p-3 text-sm",
239
- children: [/* @__PURE__ */ z("div", {
295
+ children: [/* @__PURE__ */ U("div", {
240
296
  className: "flex items-center justify-between gap-2",
241
- children: [/* @__PURE__ */ R("span", {
297
+ children: [/* @__PURE__ */ H("span", {
242
298
  className: "whitespace-nowrap text-xs text-text-tertiary",
243
- children: x(e.date)
244
- }), /* @__PURE__ */ z("span", {
299
+ children: A(e.date)
300
+ }), /* @__PURE__ */ U("span", {
245
301
  className: "flex items-center gap-2",
246
- children: [/* @__PURE__ */ R("span", {
302
+ children: [/* @__PURE__ */ H("span", {
247
303
  className: "font-medium text-text-primary",
248
- children: S(e.minutes)
249
- }), /* @__PURE__ */ R(v, { status: e.status })]
304
+ children: j(e.minutes)
305
+ }), /* @__PURE__ */ H(C, { status: e.status })]
250
306
  })]
251
- }), /* @__PURE__ */ R("div", {
307
+ }), /* @__PURE__ */ H("div", {
252
308
  className: "mt-1 truncate text-text-secondary",
253
309
  children: e.narrative || "Time entry"
254
310
  })]
@@ -256,21 +312,32 @@ var V = {
256
312
  })
257
313
  })]
258
314
  }),
259
- A && /* @__PURE__ */ R(K, {
260
- engagementId: a.id,
261
- onClose: () => L(!1),
315
+ B && /* @__PURE__ */ H(ue, {
316
+ engagementId: r.id,
317
+ currency: r.currency,
318
+ onClose: () => V(!1),
319
+ onDone: () => {
320
+ b.refetch(), K({
321
+ tone: "success",
322
+ text: "Practitioner assigned."
323
+ });
324
+ }
325
+ }),
326
+ E && /* @__PURE__ */ H(pe, {
327
+ engagementId: r.id,
328
+ onClose: () => D(!1),
262
329
  onDone: () => {
263
- h(), Y({
330
+ u(), K({
264
331
  tone: "success",
265
332
  text: "Deliverable added."
266
333
  });
267
334
  }
268
335
  }),
269
- H && /* @__PURE__ */ R(q, {
270
- engagementId: a.id,
271
- onClose: () => U(!1),
336
+ k && /* @__PURE__ */ H(me, {
337
+ engagementId: r.id,
338
+ onClose: () => F(!1),
272
339
  onDone: () => {
273
- h(), Y({
340
+ u(), K({
274
341
  tone: "success",
275
342
  text: "Milestone added."
276
343
  });
@@ -278,9 +345,176 @@ var V = {
278
345
  })
279
346
  ]
280
347
  });
281
- }, W = ({ id: e, engagementId: t, status: n, onDone: r }) => {
282
- let i = a();
283
- return /* @__PURE__ */ R(h, {
348
+ }, le = ({ assignment: e, currency: t, actors: n, onRemoved: r }) => {
349
+ let i = s(), a = n.nameOf(e.practitionerId), o = n.profiles[e.practitionerId]?.email;
350
+ return /* @__PURE__ */ U("tr", {
351
+ className: "transition-colors hover:bg-bg-sunken/60",
352
+ children: [
353
+ /* @__PURE__ */ U(T, { children: [/* @__PURE__ */ U("div", {
354
+ className: "flex items-center gap-2",
355
+ children: [/* @__PURE__ */ H("span", {
356
+ className: "font-medium text-text-primary",
357
+ children: a
358
+ }), e.isLead && /* @__PURE__ */ H("span", {
359
+ className: "rounded-full bg-bg-sunken px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-accent",
360
+ children: "Lead"
361
+ })]
362
+ }), o ? /* @__PURE__ */ H("div", {
363
+ className: "text-xs text-text-tertiary",
364
+ children: o
365
+ }) : null] }),
366
+ /* @__PURE__ */ H(T, {
367
+ className: "whitespace-nowrap",
368
+ children: N(e.role)
369
+ }),
370
+ /* @__PURE__ */ U(T, {
371
+ className: "whitespace-nowrap",
372
+ children: [e.allocationPct, "%"]
373
+ }),
374
+ /* @__PURE__ */ H(T, {
375
+ className: "whitespace-nowrap",
376
+ children: e.billingRate > 0 ? /* @__PURE__ */ U(V, { children: [M(e.billingRate, t), /* @__PURE__ */ H("span", {
377
+ className: "text-xs text-text-tertiary",
378
+ children: " / hr"
379
+ })] }) : /* @__PURE__ */ H("span", {
380
+ className: "text-text-tertiary",
381
+ children: "—"
382
+ })
383
+ }),
384
+ /* @__PURE__ */ H(T, {
385
+ className: "text-right",
386
+ children: /* @__PURE__ */ H(W, {
387
+ variant: "ghost",
388
+ size: "sm",
389
+ "aria-label": `Remove ${a}`,
390
+ disabled: i.isPending,
391
+ onClick: () => {
392
+ window.confirm(`Remove ${a} from this engagement?`) && i.mutate(e.id, {
393
+ onSuccess: (e) => r(!!e.pswRemoveTeamAssignment),
394
+ onError: () => r(!1)
395
+ });
396
+ },
397
+ children: /* @__PURE__ */ H(B, { className: "h-4 w-4" })
398
+ })
399
+ })
400
+ ]
401
+ });
402
+ }, ue = ({ engagementId: n, currency: r, onClose: i, onDone: a }) => {
403
+ let { workspaceId: o } = e(), s = G({ workspaceId: o ?? "" }), c = t(), [l, u] = P(""), [d, f] = P("ASSOCIATE"), [m, h] = P(!1), [_, v] = P("100"), [x, S] = P(""), [C, w] = P(null), T = () => {
404
+ if (w(null), !l) return w("Choose a practitioner to assign.");
405
+ let e = Number(_);
406
+ if (!Number.isFinite(e) || e < 1 || e > 100) return w("Allocation must be between 1 and 100.");
407
+ let t = x.trim() ? Number(x) : 0;
408
+ if (!Number.isFinite(t) || t < 0) return w("Billing rate must be a positive number.");
409
+ c.mutate({
410
+ engagementId: n,
411
+ practitionerId: l,
412
+ role: d,
413
+ isLead: m,
414
+ allocationPct: Math.round(e),
415
+ billingRate: t
416
+ }, {
417
+ onSuccess: () => {
418
+ a(), i();
419
+ },
420
+ onError: (e) => w(e instanceof Error ? e.message : "Could not assign.")
421
+ });
422
+ }, E = !s.loading && s.members.length === 0;
423
+ return /* @__PURE__ */ H(y, {
424
+ open: !0,
425
+ onClose: i,
426
+ title: "Assign practitioner",
427
+ footer: /* @__PURE__ */ U(V, { children: [/* @__PURE__ */ H(W, {
428
+ variant: "outline",
429
+ onClick: i,
430
+ disabled: c.isPending,
431
+ children: "Cancel"
432
+ }), /* @__PURE__ */ H(W, {
433
+ onClick: T,
434
+ disabled: c.isPending || E,
435
+ children: c.isPending ? "Assigning…" : "Assign"
436
+ })] }),
437
+ children: /* @__PURE__ */ U("div", {
438
+ className: "space-y-3",
439
+ children: [
440
+ C && /* @__PURE__ */ H(p, {
441
+ tone: "danger",
442
+ children: C
443
+ }),
444
+ E && /* @__PURE__ */ H(p, {
445
+ tone: "danger",
446
+ children: "The workspace member directory could not be read, so there is nobody to assign here."
447
+ }),
448
+ /* @__PURE__ */ H(g, {
449
+ label: "Practitioner",
450
+ required: !0,
451
+ children: /* @__PURE__ */ U(b, {
452
+ value: l,
453
+ onChange: (e) => u(e.target.value),
454
+ disabled: s.loading || E,
455
+ children: [/* @__PURE__ */ H("option", {
456
+ value: "",
457
+ children: s.loading ? "Loading members…" : "Select a member"
458
+ }), s.members.map((e) => /* @__PURE__ */ H("option", {
459
+ value: e.id,
460
+ children: e.email ? `${e.name} · ${e.email}` : e.name
461
+ }, e.id))]
462
+ })
463
+ }),
464
+ s.hasMore && /* @__PURE__ */ H(W, {
465
+ variant: "outline",
466
+ size: "sm",
467
+ onClick: s.fetchMore,
468
+ disabled: s.loading,
469
+ children: "Load more members"
470
+ }),
471
+ /* @__PURE__ */ U("div", {
472
+ className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
473
+ children: [/* @__PURE__ */ H(g, {
474
+ label: "Role",
475
+ children: /* @__PURE__ */ H(b, {
476
+ value: d,
477
+ onChange: (e) => f(e.target.value),
478
+ children: k(q)
479
+ })
480
+ }), /* @__PURE__ */ H(g, {
481
+ label: "Allocation %",
482
+ hint: "Share of this person's time on the engagement.",
483
+ children: /* @__PURE__ */ H(D, {
484
+ type: "number",
485
+ min: 1,
486
+ max: 100,
487
+ value: _,
488
+ onChange: (e) => v(e.target.value)
489
+ })
490
+ })]
491
+ }),
492
+ /* @__PURE__ */ H(g, {
493
+ label: `Billing rate (${r} / hr)`,
494
+ hint: "Leave blank for a non-billed seat.",
495
+ children: /* @__PURE__ */ H(D, {
496
+ type: "number",
497
+ min: 0,
498
+ value: x,
499
+ onChange: (e) => S(e.target.value),
500
+ placeholder: "0"
501
+ })
502
+ }),
503
+ /* @__PURE__ */ U("label", {
504
+ className: "flex items-center gap-2 text-sm text-text-secondary",
505
+ children: [/* @__PURE__ */ H("input", {
506
+ type: "checkbox",
507
+ checked: m,
508
+ onChange: (e) => h(e.target.checked),
509
+ className: "h-4 w-4 rounded border-border-seam accent-accent"
510
+ }), "Engagement lead"]
511
+ })
512
+ ]
513
+ })
514
+ });
515
+ }, de = ({ id: e, engagementId: t, status: n, onDone: r }) => {
516
+ let i = u();
517
+ return /* @__PURE__ */ H(b, {
284
518
  value: n,
285
519
  disabled: i.isPending,
286
520
  onChange: (n) => i.mutate({
@@ -289,46 +523,46 @@ var V = {
289
523
  engagementId: t
290
524
  }, { onSuccess: r }),
291
525
  className: "w-40 shrink-0",
292
- children: H.map((e) => /* @__PURE__ */ R("option", {
526
+ children: K.map((e) => /* @__PURE__ */ H("option", {
293
527
  value: e,
294
- children: w(e)
528
+ children: N(e)
295
529
  }, e))
296
530
  });
297
- }, G = ({ id: t, engagementId: n, onDone: r }) => {
298
- let i = e();
299
- return /* @__PURE__ */ R(B, {
531
+ }, fe = ({ id: e, engagementId: t, onDone: r }) => {
532
+ let i = n();
533
+ return /* @__PURE__ */ H(W, {
300
534
  variant: "ghost",
301
535
  size: "sm",
302
536
  "aria-label": "Complete milestone",
303
537
  disabled: i.isPending,
304
538
  onClick: () => i.mutate({
305
- id: t,
306
- engagementId: n
539
+ id: e,
540
+ engagementId: t
307
541
  }, { onSuccess: r }),
308
- children: /* @__PURE__ */ R(A, { className: "h-4 w-4" })
542
+ children: /* @__PURE__ */ H(F, { className: "h-4 w-4" })
309
543
  });
310
- }, K = ({ engagementId: e, onClose: n, onDone: r }) => {
311
- let i = t(), [a, o] = D({
544
+ }, pe = ({ engagementId: e, onClose: t, onDone: n }) => {
545
+ let i = r(), [a, o] = P({
312
546
  name: "",
313
547
  description: "",
314
548
  dueDate: "",
315
549
  acceptanceCriteria: ""
316
- }), [s, l] = D(null), u = (e) => (t) => o((n) => ({
550
+ }), [s, c] = P(null), l = (e) => (t) => o((n) => ({
317
551
  ...n,
318
552
  [e]: t.target.value
319
553
  }));
320
- return /* @__PURE__ */ R(m, {
554
+ return /* @__PURE__ */ H(y, {
321
555
  open: !0,
322
- onClose: n,
556
+ onClose: t,
323
557
  title: "Add deliverable",
324
- footer: /* @__PURE__ */ z(L, { children: [/* @__PURE__ */ R(B, {
558
+ footer: /* @__PURE__ */ U(V, { children: [/* @__PURE__ */ H(W, {
325
559
  variant: "outline",
326
- onClick: n,
560
+ onClick: t,
327
561
  disabled: i.isPending,
328
562
  children: "Cancel"
329
- }), /* @__PURE__ */ R(B, {
563
+ }), /* @__PURE__ */ H(W, {
330
564
  onClick: () => {
331
- if (l(null), !a.name.trim()) return l("Name is required.");
565
+ if (c(null), !a.name.trim()) return c("Name is required.");
332
566
  i.mutate({
333
567
  engagementId: e,
334
568
  name: a.name.trim(),
@@ -337,75 +571,75 @@ var V = {
337
571
  dueDate: a.dueDate ? new Date(a.dueDate).toISOString() : null
338
572
  }, {
339
573
  onSuccess: () => {
340
- r(), n();
574
+ n(), t();
341
575
  },
342
- onError: (e) => l(e instanceof Error ? e.message : "Failed.")
576
+ onError: (e) => c(e instanceof Error ? e.message : "Failed.")
343
577
  });
344
578
  },
345
579
  disabled: i.isPending,
346
580
  children: i.isPending ? "Adding…" : "Add"
347
581
  })] }),
348
- children: /* @__PURE__ */ z("div", {
582
+ children: /* @__PURE__ */ U("div", {
349
583
  className: "space-y-3",
350
584
  children: [
351
- s && /* @__PURE__ */ R(c, {
585
+ s && /* @__PURE__ */ H(p, {
352
586
  tone: "danger",
353
587
  children: s
354
588
  }),
355
- /* @__PURE__ */ R(d, {
589
+ /* @__PURE__ */ H(g, {
356
590
  label: "Name",
357
591
  required: !0,
358
- children: /* @__PURE__ */ R(b, {
592
+ children: /* @__PURE__ */ H(D, {
359
593
  value: a.name,
360
- onChange: u("name"),
594
+ onChange: l("name"),
361
595
  placeholder: "Draft audit report"
362
596
  })
363
597
  }),
364
- /* @__PURE__ */ R(d, {
598
+ /* @__PURE__ */ H(g, {
365
599
  label: "Due date",
366
- children: /* @__PURE__ */ R(b, {
600
+ children: /* @__PURE__ */ H(D, {
367
601
  type: "date",
368
602
  value: a.dueDate,
369
- onChange: u("dueDate")
603
+ onChange: l("dueDate")
370
604
  })
371
605
  }),
372
- /* @__PURE__ */ R(d, {
606
+ /* @__PURE__ */ H(g, {
373
607
  label: "Acceptance criteria",
374
- children: /* @__PURE__ */ R(y, {
608
+ children: /* @__PURE__ */ H(E, {
375
609
  value: a.acceptanceCriteria,
376
- onChange: u("acceptanceCriteria")
610
+ onChange: l("acceptanceCriteria")
377
611
  })
378
612
  }),
379
- /* @__PURE__ */ R(d, {
613
+ /* @__PURE__ */ H(g, {
380
614
  label: "Description",
381
- children: /* @__PURE__ */ R(y, {
615
+ children: /* @__PURE__ */ H(E, {
382
616
  value: a.description,
383
- onChange: u("description")
617
+ onChange: l("description")
384
618
  })
385
619
  })
386
620
  ]
387
621
  })
388
622
  });
389
- }, q = ({ engagementId: e, onClose: t, onDone: r }) => {
390
- let i = n(), [a, o] = D({
623
+ }, me = ({ engagementId: e, onClose: t, onDone: n }) => {
624
+ let r = i(), [a, o] = P({
391
625
  name: "",
392
626
  dueDate: "",
393
627
  triggerAmount: "",
394
628
  triggersInvoice: !1
395
- }), [s, l] = D(null);
396
- return /* @__PURE__ */ R(m, {
629
+ }), [s, c] = P(null);
630
+ return /* @__PURE__ */ H(y, {
397
631
  open: !0,
398
632
  onClose: t,
399
633
  title: "Add milestone",
400
- footer: /* @__PURE__ */ z(L, { children: [/* @__PURE__ */ R(B, {
634
+ footer: /* @__PURE__ */ U(V, { children: [/* @__PURE__ */ H(W, {
401
635
  variant: "outline",
402
636
  onClick: t,
403
- disabled: i.isPending,
637
+ disabled: r.isPending,
404
638
  children: "Cancel"
405
- }), /* @__PURE__ */ R(B, {
639
+ }), /* @__PURE__ */ H(W, {
406
640
  onClick: () => {
407
- if (l(null), !a.name.trim()) return l("Name is required.");
408
- i.mutate({
641
+ if (c(null), !a.name.trim()) return c("Name is required.");
642
+ r.mutate({
409
643
  engagementId: e,
410
644
  name: a.name.trim(),
411
645
  dueDate: a.dueDate ? new Date(a.dueDate).toISOString() : null,
@@ -413,25 +647,25 @@ var V = {
413
647
  triggersInvoice: a.triggersInvoice
414
648
  }, {
415
649
  onSuccess: () => {
416
- r(), t();
650
+ n(), t();
417
651
  },
418
- onError: (e) => l(e instanceof Error ? e.message : "Failed.")
652
+ onError: (e) => c(e instanceof Error ? e.message : "Failed.")
419
653
  });
420
654
  },
421
- disabled: i.isPending,
422
- children: i.isPending ? "Adding…" : "Add"
655
+ disabled: r.isPending,
656
+ children: r.isPending ? "Adding…" : "Add"
423
657
  })] }),
424
- children: /* @__PURE__ */ z("div", {
658
+ children: /* @__PURE__ */ U("div", {
425
659
  className: "space-y-3",
426
660
  children: [
427
- s && /* @__PURE__ */ R(c, {
661
+ s && /* @__PURE__ */ H(p, {
428
662
  tone: "danger",
429
663
  children: s
430
664
  }),
431
- /* @__PURE__ */ R(d, {
665
+ /* @__PURE__ */ H(g, {
432
666
  label: "Name",
433
667
  required: !0,
434
- children: /* @__PURE__ */ R(b, {
668
+ children: /* @__PURE__ */ H(D, {
435
669
  value: a.name,
436
670
  onChange: (e) => o((t) => ({
437
671
  ...t,
@@ -440,9 +674,9 @@ var V = {
440
674
  placeholder: "Phase 1 sign-off"
441
675
  })
442
676
  }),
443
- /* @__PURE__ */ R(d, {
677
+ /* @__PURE__ */ H(g, {
444
678
  label: "Due date",
445
- children: /* @__PURE__ */ R(b, {
679
+ children: /* @__PURE__ */ H(D, {
446
680
  type: "date",
447
681
  value: a.dueDate,
448
682
  onChange: (e) => o((t) => ({
@@ -451,10 +685,10 @@ var V = {
451
685
  }))
452
686
  })
453
687
  }),
454
- /* @__PURE__ */ R(d, {
688
+ /* @__PURE__ */ H(g, {
455
689
  label: "Trigger amount",
456
690
  hint: "Amount billed when this milestone completes (if it triggers an invoice).",
457
- children: /* @__PURE__ */ R(b, {
691
+ children: /* @__PURE__ */ H(D, {
458
692
  type: "number",
459
693
  min: "0",
460
694
  value: a.triggerAmount,
@@ -464,9 +698,9 @@ var V = {
464
698
  }))
465
699
  })
466
700
  }),
467
- /* @__PURE__ */ z("label", {
701
+ /* @__PURE__ */ U("label", {
468
702
  className: "flex items-center gap-2 text-sm text-text-secondary",
469
- children: [/* @__PURE__ */ R("input", {
703
+ children: [/* @__PURE__ */ H("input", {
470
704
  type: "checkbox",
471
705
  checked: a.triggersInvoice,
472
706
  onChange: (e) => o((t) => ({
@@ -480,6 +714,6 @@ var V = {
480
714
  });
481
715
  };
482
716
  //#endregion
483
- export { U as EngagementDetailPage };
717
+ export { J as EngagementDetailPage };
484
718
 
485
719
  //# sourceMappingURL=EngagementDetailPage.js.map