@burdenoff/microfe-proserviceworld 2026.706.1 → 2026.706.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/proserviceworld/components/DiscussionThread.js +76 -0
  2. package/dist/proserviceworld/components/DiscussionThread.js.map +1 -0
  3. package/dist/proserviceworld/components/ui.js +18 -18
  4. package/dist/proserviceworld/components/ui.js.map +1 -1
  5. package/dist/proserviceworld/hooks/useEntityComments.js +56 -0
  6. package/dist/proserviceworld/hooks/useEntityComments.js.map +1 -0
  7. package/dist/proserviceworld/pages/AnalyticsPage.js +277 -111
  8. package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
  9. package/dist/proserviceworld/pages/ClientDetailPage.js +222 -149
  10. package/dist/proserviceworld/pages/ClientDetailPage.js.map +1 -1
  11. package/dist/proserviceworld/pages/ClientsPage.js +148 -127
  12. package/dist/proserviceworld/pages/ClientsPage.js.map +1 -1
  13. package/dist/proserviceworld/pages/CompliancePage.js +140 -96
  14. package/dist/proserviceworld/pages/CompliancePage.js.map +1 -1
  15. package/dist/proserviceworld/pages/DashboardPage.js +221 -104
  16. package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
  17. package/dist/proserviceworld/pages/EngagementDetailPage.js +217 -182
  18. package/dist/proserviceworld/pages/EngagementDetailPage.js.map +1 -1
  19. package/dist/proserviceworld/pages/EngagementsPage.js +144 -106
  20. package/dist/proserviceworld/pages/EngagementsPage.js.map +1 -1
  21. package/dist/proserviceworld/pages/InvoicesPage.js +144 -129
  22. package/dist/proserviceworld/pages/InvoicesPage.js.map +1 -1
  23. package/dist/proserviceworld/pages/MattersPage.js +163 -122
  24. package/dist/proserviceworld/pages/MattersPage.js.map +1 -1
  25. package/dist/proserviceworld/pages/TimePage.js +112 -92
  26. package/dist/proserviceworld/pages/TimePage.js.map +1 -1
  27. package/package.json +1 -1
@@ -1,229 +1,302 @@
1
1
  import { useClient as e, useConflictChecks as t, useCreateConflictCheck as n } from "../hooks/useProServiceWorldApi.js";
2
2
  import { useNav as r } from "../utils/nav.js";
3
- import { Banner as i, Empty as a, ErrorState as o, Field as s, LoadingBlock as c, Modal as l, PageHeader as u, SectionCard as d, StatusPill as f, TextArea as p, TextInput as m, fmtDate as h, fmtMoney as g } from "../components/ui.js";
4
- import { useState as _ } from "react";
5
- import { ArrowLeft as v, Briefcase as y, FolderKanban as b, GitBranch as x, Mail as S, MapPin as C, Phone as w, Plus as T } from "lucide-react";
6
- import { useParams as E } from "react-router-dom";
7
- import { Fragment as D, jsx as O, jsxs as k } from "react/jsx-runtime";
8
- import { Button as A } from "@burdenoff/fe-libs/ui";
3
+ import { cn as i } from "../utils/cn.js";
4
+ import { Banner as a, Empty as o, ErrorState as s, Field as c, LoadingBlock as l, Modal as u, PageHeader as d, SectionCard as f, StatusPill as p, TableWrap as m, Td as h, TextArea as g, TextInput as _, Th as v, fmtDate as y, fmtMoney as b, humanize as x } from "../components/ui.js";
5
+ import { DiscussionThread as S } from "../components/DiscussionThread.js";
6
+ import { useState as C } from "react";
7
+ import { ArrowLeft as w, Briefcase as T, ChevronRight as E, FolderKanban as D, GitBranch as O, Mail as k, MapPin as A, Phone as j, Plus as M, StickyNote as N } from "lucide-react";
8
+ import { useParams as P } from "react-router-dom";
9
+ import { Fragment as F, jsx as I, jsxs as L } from "react/jsx-runtime";
10
+ import { Button as R } from "@burdenoff/fe-libs/ui";
9
11
  //#region src/proserviceworld/pages/ClientDetailPage.tsx
10
- var j = () => {
11
- let { id: n = "" } = E(), i = r(), { data: s, isLoading: l, error: p, refetch: m } = e(n), D = t({ clientId: n }), [j, F] = _(!1);
12
- return l ? /* @__PURE__ */ O(c, {}) : p ? /* @__PURE__ */ O(o, {
13
- error: p,
14
- onRetry: () => void m()
15
- }) : s ? /* @__PURE__ */ k("div", {
12
+ var z = (e) => e.split(/\s+/).filter(Boolean).slice(0, 2).map((e) => e[0]?.toUpperCase() ?? "").join("") || "?", B = () => {
13
+ let { id: n = "" } = P(), i = r(), { data: a, isLoading: c, error: u, refetch: g } = e(n), _ = t({ clientId: n }), [F, B] = C(!1);
14
+ if (c) return /* @__PURE__ */ I(l, {});
15
+ if (u) return /* @__PURE__ */ I(s, {
16
+ error: u,
17
+ onRetry: () => void g()
18
+ });
19
+ if (!a) return /* @__PURE__ */ I(o, {
20
+ title: "Client not found",
21
+ description: "It may have been archived or belongs to another workspace."
22
+ });
23
+ let U = a.contactName || a.name;
24
+ return /* @__PURE__ */ L("div", {
16
25
  className: "space-y-6",
17
26
  children: [
18
- /* @__PURE__ */ k("button", {
27
+ /* @__PURE__ */ L("button", {
19
28
  type: "button",
20
29
  onClick: () => i("clients"),
21
- className: "flex items-center gap-1.5 text-sm text-text-secondary hover:text-accent",
22
- children: [/* @__PURE__ */ O(v, { className: "h-4 w-4" }), " Back to clients"]
30
+ className: "flex items-center gap-1.5 text-sm text-text-secondary transition-colors hover:text-accent",
31
+ children: [/* @__PURE__ */ I(w, { className: "h-4 w-4" }), " Back to clients"]
23
32
  }),
24
- /* @__PURE__ */ O(u, {
25
- title: s.name,
26
- subtitle: [s.type === "ORGANIZATION" ? "Organization" : "Individual", s.industry].filter(Boolean).join(" · "),
27
- actions: /* @__PURE__ */ O(f, { status: s.status })
33
+ /* @__PURE__ */ I(d, {
34
+ title: a.name,
35
+ subtitle: [a.type === "ORGANIZATION" ? "Organization" : "Individual", a.industry].filter(Boolean).join(" · "),
36
+ actions: /* @__PURE__ */ I(p, { status: a.status })
28
37
  }),
29
- /* @__PURE__ */ k("div", {
38
+ a.tags.length > 0 && /* @__PURE__ */ I("div", {
39
+ className: "flex flex-wrap gap-2",
40
+ children: a.tags.map((e) => /* @__PURE__ */ I("span", {
41
+ className: "rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary",
42
+ children: e
43
+ }, e))
44
+ }),
45
+ /* @__PURE__ */ L("div", {
30
46
  className: "grid grid-cols-1 gap-4 lg:grid-cols-3",
31
- children: [/* @__PURE__ */ O(d, {
47
+ children: [/* @__PURE__ */ I(f, {
32
48
  title: "Contact",
33
49
  className: "lg:col-span-1",
34
- children: /* @__PURE__ */ k("dl", {
35
- className: "space-y-2 text-sm",
36
- children: [
37
- /* @__PURE__ */ O(M, {
38
- label: "Contact",
39
- value: s.contactName
40
- }),
41
- s.contactEmail && /* @__PURE__ */ O(N, {
42
- icon: /* @__PURE__ */ O(S, { className: "h-3.5 w-3.5" }),
43
- value: s.contactEmail
44
- }),
45
- s.contactPhone && /* @__PURE__ */ O(N, {
46
- icon: /* @__PURE__ */ O(w, { className: "h-3.5 w-3.5" }),
47
- value: s.contactPhone
48
- }),
49
- s.address && /* @__PURE__ */ O(N, {
50
- icon: /* @__PURE__ */ O(C, { className: "h-3.5 w-3.5" }),
51
- value: s.address
52
- }),
53
- s.notes && /* @__PURE__ */ O("p", {
54
- className: "pt-2 text-text-secondary",
55
- children: s.notes
56
- })
57
- ]
50
+ children: /* @__PURE__ */ L("div", {
51
+ className: "space-y-4",
52
+ children: [/* @__PURE__ */ L("div", {
53
+ className: "flex items-center gap-3",
54
+ children: [/* @__PURE__ */ I("span", {
55
+ className: "flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-accent/10 text-sm font-semibold text-accent ring-1 ring-inset ring-accent/15",
56
+ children: z(U)
57
+ }), /* @__PURE__ */ L("div", {
58
+ className: "min-w-0",
59
+ children: [/* @__PURE__ */ I("div", {
60
+ className: "truncate text-sm font-semibold text-text-primary",
61
+ children: a.contactName || a.name
62
+ }), /* @__PURE__ */ I("div", {
63
+ className: "truncate text-xs text-text-tertiary",
64
+ children: a.contactName ? "Primary contact" : "Client record"
65
+ })]
66
+ })]
67
+ }), /* @__PURE__ */ L("dl", {
68
+ className: "space-y-2.5 text-sm",
69
+ children: [
70
+ a.contactEmail && /* @__PURE__ */ I(V, {
71
+ icon: /* @__PURE__ */ I(k, { className: "h-4 w-4" }),
72
+ value: a.contactEmail
73
+ }),
74
+ a.contactPhone && /* @__PURE__ */ I(V, {
75
+ icon: /* @__PURE__ */ I(j, { className: "h-4 w-4" }),
76
+ value: a.contactPhone
77
+ }),
78
+ a.address && /* @__PURE__ */ I(V, {
79
+ icon: /* @__PURE__ */ I(A, { className: "h-4 w-4" }),
80
+ value: a.address
81
+ }),
82
+ a.notes && /* @__PURE__ */ I(V, {
83
+ icon: /* @__PURE__ */ I(N, { className: "h-4 w-4" }),
84
+ value: a.notes,
85
+ multiline: !0
86
+ }),
87
+ !a.contactEmail && !a.contactPhone && !a.address && !a.notes && /* @__PURE__ */ I("p", {
88
+ className: "text-sm text-text-tertiary",
89
+ children: "No contact details recorded yet."
90
+ })
91
+ ]
92
+ })]
58
93
  })
59
- }), /* @__PURE__ */ O(d, {
94
+ }), /* @__PURE__ */ I(f, {
60
95
  title: "Conflict checks",
61
96
  className: "lg:col-span-2",
62
- action: /* @__PURE__ */ k(A, {
97
+ action: /* @__PURE__ */ L(R, {
63
98
  size: "sm",
64
99
  variant: "outline",
65
- onClick: () => F(!0),
66
- children: [/* @__PURE__ */ O(x, { className: "mr-1.5 h-4 w-4" }), " Run check"]
100
+ onClick: () => B(!0),
101
+ children: [/* @__PURE__ */ I(O, { className: "mr-1.5 h-4 w-4" }), " Run check"]
67
102
  }),
68
- children: D.isLoading ? /* @__PURE__ */ O(c, { label: "Loading…" }) : !D.data || D.data.length === 0 ? /* @__PURE__ */ O(a, {
103
+ children: _.isLoading ? /* @__PURE__ */ I(l, { label: "Loading…" }) : !_.data || _.data.length === 0 ? /* @__PURE__ */ I(o, {
69
104
  title: "No conflict checks",
70
- description: "Run a conflict check before accepting new work for this client."
71
- }) : /* @__PURE__ */ O("ul", {
72
- className: "divide-y divide-border-seam",
73
- children: D.data.map((e) => /* @__PURE__ */ k("li", {
74
- className: "flex items-center justify-between py-2.5",
75
- children: [/* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("div", {
76
- className: "text-sm text-text-primary",
105
+ description: "Run a conflict check before accepting new work for this client.",
106
+ icon: /* @__PURE__ */ I(O, { className: "h-7 w-7" })
107
+ }) : /* @__PURE__ */ L(m, { children: [/* @__PURE__ */ I("thead", { children: /* @__PURE__ */ L("tr", { children: [
108
+ /* @__PURE__ */ I(v, { children: "Parties searched" }),
109
+ /* @__PURE__ */ I(v, { children: "Date" }),
110
+ /* @__PURE__ */ I(v, {
111
+ className: "text-right",
112
+ children: "Status"
113
+ })
114
+ ] }) }), /* @__PURE__ */ I("tbody", { children: _.data.map((e) => /* @__PURE__ */ L("tr", {
115
+ className: "transition-colors hover:bg-bg-sunken/60",
116
+ children: [
117
+ /* @__PURE__ */ L(h, { children: [/* @__PURE__ */ I("div", {
118
+ className: "font-medium text-text-primary",
77
119
  children: e.searchedParties.join(", ") || "Conflict search"
78
- }), /* @__PURE__ */ k("div", {
120
+ }), e.findings ? /* @__PURE__ */ I("div", {
79
121
  className: "text-xs text-text-tertiary",
80
- children: [h(e.createdAt), e.findings ? ` · ${e.findings}` : ""]
81
- })] }), /* @__PURE__ */ O(f, { status: e.status })]
82
- }, e.id))
83
- })
122
+ children: e.findings
123
+ }) : null] }),
124
+ /* @__PURE__ */ I(h, {
125
+ className: "whitespace-nowrap",
126
+ children: y(e.createdAt)
127
+ }),
128
+ /* @__PURE__ */ I(h, {
129
+ className: "text-right",
130
+ children: /* @__PURE__ */ I(p, { status: e.status })
131
+ })
132
+ ]
133
+ }, e.id)) })] })
84
134
  })]
85
135
  }),
86
- /* @__PURE__ */ O(d, {
136
+ /* @__PURE__ */ I(f, {
87
137
  title: "Engagements",
88
- action: /* @__PURE__ */ k(A, {
138
+ description: `${a.engagements.length} scoped for this client`,
139
+ action: /* @__PURE__ */ L(R, {
89
140
  size: "sm",
90
141
  variant: "outline",
91
142
  onClick: () => i("engagements"),
92
- children: [/* @__PURE__ */ O(T, { className: "mr-1.5 h-4 w-4" }), "Manage"]
143
+ children: [/* @__PURE__ */ I(M, { className: "mr-1.5 h-4 w-4" }), "Manage"]
93
144
  }),
94
- children: s.engagements.length === 0 ? /* @__PURE__ */ O(a, {
145
+ children: a.engagements.length === 0 ? /* @__PURE__ */ I(o, {
95
146
  title: "No engagements",
96
147
  description: "Scope the first engagement for this client.",
97
- icon: /* @__PURE__ */ O(y, { className: "h-7 w-7" })
98
- }) : /* @__PURE__ */ O("ul", {
99
- className: "divide-y divide-border-seam",
100
- children: s.engagements.map((e) => /* @__PURE__ */ O("li", { children: /* @__PURE__ */ k("button", {
101
- type: "button",
102
- onClick: () => i(`engagements/${e.id}`),
103
- className: "flex w-full items-center justify-between py-2.5 text-left hover:text-accent",
104
- children: [/* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("div", {
105
- className: "text-sm font-medium text-text-primary",
106
- children: e.name
107
- }), /* @__PURE__ */ k("div", {
108
- className: "text-xs text-text-tertiary",
148
+ icon: /* @__PURE__ */ I(T, { className: "h-7 w-7" })
149
+ }) : /* @__PURE__ */ L(m, { children: [/* @__PURE__ */ I("thead", { children: /* @__PURE__ */ L("tr", { children: [
150
+ /* @__PURE__ */ I(v, { children: "Engagement" }),
151
+ /* @__PURE__ */ I(v, { children: "Fee" }),
152
+ /* @__PURE__ */ I(v, { children: "Started" }),
153
+ /* @__PURE__ */ I(v, {
154
+ className: "text-right",
155
+ children: "Status"
156
+ })
157
+ ] }) }), /* @__PURE__ */ I("tbody", { children: a.engagements.map((e) => /* @__PURE__ */ L("tr", {
158
+ className: "group cursor-pointer transition-colors hover:bg-bg-sunken/60",
159
+ onClick: () => i(`engagements/${e.id}`),
160
+ children: [
161
+ /* @__PURE__ */ I(h, { children: /* @__PURE__ */ L("div", {
162
+ className: "flex items-center gap-1 font-medium text-text-primary transition-colors group-hover:text-accent",
163
+ children: [/* @__PURE__ */ I("span", { children: e.name }), /* @__PURE__ */ I(E, { className: "h-3.5 w-3.5 shrink-0 text-text-tertiary opacity-0 transition-opacity group-hover:opacity-100" })]
164
+ }) }),
165
+ /* @__PURE__ */ L(h, {
166
+ className: "whitespace-nowrap",
109
167
  children: [
110
- g(e.feeAmount, e.currency),
111
- " · ",
112
- e.feeArrangement.toLowerCase()
168
+ b(e.feeAmount, e.currency),
169
+ " ",
170
+ /* @__PURE__ */ L("span", {
171
+ className: "text-xs text-text-tertiary",
172
+ children: ["/ ", x(e.feeArrangement)]
173
+ })
113
174
  ]
114
- })] }), /* @__PURE__ */ O(f, { status: e.status })]
115
- }) }, e.id))
116
- })
175
+ }),
176
+ /* @__PURE__ */ I(h, {
177
+ className: "whitespace-nowrap",
178
+ children: y(e.startDate)
179
+ }),
180
+ /* @__PURE__ */ I(h, {
181
+ className: "text-right",
182
+ children: /* @__PURE__ */ I(p, { status: e.status })
183
+ })
184
+ ]
185
+ }, e.id)) })] })
117
186
  }),
118
- /* @__PURE__ */ O(d, {
187
+ /* @__PURE__ */ I(f, {
119
188
  title: "Matters",
120
- action: /* @__PURE__ */ k(A, {
189
+ description: `${a.matters.length} tracked for this client`,
190
+ action: /* @__PURE__ */ L(R, {
121
191
  size: "sm",
122
192
  variant: "outline",
123
193
  onClick: () => i("matters"),
124
- children: [/* @__PURE__ */ O(T, { className: "mr-1.5 h-4 w-4" }), "Manage"]
194
+ children: [/* @__PURE__ */ I(M, { className: "mr-1.5 h-4 w-4" }), "Manage"]
125
195
  }),
126
- children: s.matters.length === 0 ? /* @__PURE__ */ O(a, {
196
+ children: a.matters.length === 0 ? /* @__PURE__ */ I(o, {
127
197
  title: "No matters",
128
198
  description: "Open a matter to track regulated work for this client.",
129
- icon: /* @__PURE__ */ O(b, { className: "h-7 w-7" })
130
- }) : /* @__PURE__ */ O("ul", {
131
- className: "divide-y divide-border-seam",
132
- children: s.matters.map((e) => /* @__PURE__ */ k("li", {
133
- className: "flex items-center justify-between py-2.5",
134
- children: [/* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("div", {
135
- className: "text-sm font-medium text-text-primary",
136
- children: e.title
137
- }), /* @__PURE__ */ k("div", {
138
- className: "text-xs text-text-tertiary",
139
- children: [
140
- e.reference || "No reference",
141
- " · Due ",
142
- h(e.dueDate)
143
- ]
144
- })] }), /* @__PURE__ */ k("div", {
145
- className: "flex items-center gap-2",
146
- children: [/* @__PURE__ */ O(f, { status: e.priority }), /* @__PURE__ */ O(f, { status: e.status })]
147
- })]
148
- }, e.id))
149
- })
199
+ icon: /* @__PURE__ */ I(D, { className: "h-7 w-7" })
200
+ }) : /* @__PURE__ */ L(m, { children: [/* @__PURE__ */ I("thead", { children: /* @__PURE__ */ L("tr", { children: [
201
+ /* @__PURE__ */ I(v, { children: "Reference" }),
202
+ /* @__PURE__ */ I(v, { children: "Title" }),
203
+ /* @__PURE__ */ I(v, { children: "Due" }),
204
+ /* @__PURE__ */ I(v, { children: "Priority" }),
205
+ /* @__PURE__ */ I(v, {
206
+ className: "text-right",
207
+ children: "Status"
208
+ })
209
+ ] }) }), /* @__PURE__ */ I("tbody", { children: a.matters.map((e) => /* @__PURE__ */ L("tr", {
210
+ className: "transition-colors hover:bg-bg-sunken/60",
211
+ children: [
212
+ /* @__PURE__ */ I(h, {
213
+ className: "whitespace-nowrap font-medium text-text-primary",
214
+ children: e.reference || "—"
215
+ }),
216
+ /* @__PURE__ */ I(h, { children: e.title }),
217
+ /* @__PURE__ */ I(h, {
218
+ className: "whitespace-nowrap",
219
+ children: y(e.dueDate)
220
+ }),
221
+ /* @__PURE__ */ I(h, { children: /* @__PURE__ */ I(p, { status: e.priority }) }),
222
+ /* @__PURE__ */ I(h, {
223
+ className: "text-right",
224
+ children: /* @__PURE__ */ I(p, { status: e.status })
225
+ })
226
+ ]
227
+ }, e.id)) })] })
228
+ }),
229
+ /* @__PURE__ */ I(S, {
230
+ entityType: "proserviceworld-client",
231
+ entityId: n
150
232
  }),
151
- j && /* @__PURE__ */ O(P, {
233
+ F && /* @__PURE__ */ I(H, {
152
234
  clientId: n,
153
- onClose: () => F(!1),
154
- onDone: () => void D.refetch()
235
+ onClose: () => B(!1),
236
+ onDone: () => void _.refetch()
155
237
  })
156
238
  ]
157
- }) : /* @__PURE__ */ O(a, {
158
- title: "Client not found",
159
- description: "It may have been archived or belongs to another workspace."
160
239
  });
161
- }, M = ({ label: e, value: t }) => t ? /* @__PURE__ */ k("div", {
162
- className: "flex justify-between gap-4",
163
- children: [/* @__PURE__ */ O("dt", {
164
- className: "text-text-tertiary",
240
+ }, V = ({ icon: e, value: t, multiline: n }) => /* @__PURE__ */ L("div", {
241
+ className: i("flex gap-2.5 text-text-secondary", n ? "items-start" : "items-center"),
242
+ children: [/* @__PURE__ */ I("span", {
243
+ className: "mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-bg-sunken text-text-tertiary",
165
244
  children: e
166
- }), /* @__PURE__ */ O("dd", {
167
- className: "text-right text-text-primary",
245
+ }), /* @__PURE__ */ I("span", {
246
+ className: i("min-w-0 break-words", n ? "leading-relaxed" : "truncate"),
168
247
  children: t
169
248
  })]
170
- }) : null, N = ({ icon: e, value: t }) => /* @__PURE__ */ k("div", {
171
- className: "flex items-center gap-2 text-text-secondary",
172
- children: [/* @__PURE__ */ O("span", {
173
- className: "text-text-tertiary",
174
- children: e
175
- }), t]
176
- }), P = ({ clientId: e, onClose: t, onDone: r }) => {
177
- let a = n(), [o, c] = _(""), [u, d] = _(""), [f, h] = _(null);
178
- return /* @__PURE__ */ O(l, {
249
+ }), H = ({ clientId: e, onClose: t, onDone: r }) => {
250
+ let i = n(), [o, s] = C(""), [l, d] = C(""), [f, p] = C(null);
251
+ return /* @__PURE__ */ I(u, {
179
252
  open: !0,
180
253
  onClose: t,
181
254
  title: "Run conflict check",
182
- footer: /* @__PURE__ */ k(D, { children: [/* @__PURE__ */ O(A, {
255
+ footer: /* @__PURE__ */ L(F, { children: [/* @__PURE__ */ I(R, {
183
256
  variant: "outline",
184
257
  onClick: t,
185
- disabled: a.isPending,
258
+ disabled: i.isPending,
186
259
  children: "Cancel"
187
- }), /* @__PURE__ */ O(A, {
260
+ }), /* @__PURE__ */ I(R, {
188
261
  onClick: () => {
189
- h(null);
262
+ p(null);
190
263
  let n = o.split(",").map((e) => e.trim()).filter(Boolean);
191
- if (n.length === 0) return h("Add at least one party to search.");
192
- a.mutate({
264
+ if (n.length === 0) return p("Add at least one party to search.");
265
+ i.mutate({
193
266
  clientId: e,
194
267
  searchedParties: n,
195
- findings: u.trim() || null
268
+ findings: l.trim() || null
196
269
  }, {
197
270
  onSuccess: () => {
198
271
  r(), t();
199
272
  },
200
- onError: (e) => h(e instanceof Error ? e.message : "Failed.")
273
+ onError: (e) => p(e instanceof Error ? e.message : "Failed.")
201
274
  });
202
275
  },
203
- disabled: a.isPending,
204
- children: a.isPending ? "Running…" : "Record check"
276
+ disabled: i.isPending,
277
+ children: i.isPending ? "Running…" : "Record check"
205
278
  })] }),
206
- children: /* @__PURE__ */ k("div", {
279
+ children: /* @__PURE__ */ L("div", {
207
280
  className: "space-y-3",
208
281
  children: [
209
- f && /* @__PURE__ */ O(i, {
282
+ f && /* @__PURE__ */ I(a, {
210
283
  tone: "danger",
211
284
  children: f
212
285
  }),
213
- /* @__PURE__ */ O(s, {
286
+ /* @__PURE__ */ I(c, {
214
287
  label: "Parties searched",
215
288
  required: !0,
216
289
  hint: "Comma-separated — counterparties, related entities, principals.",
217
- children: /* @__PURE__ */ O(m, {
290
+ children: /* @__PURE__ */ I(_, {
218
291
  value: o,
219
- onChange: (e) => c(e.target.value),
292
+ onChange: (e) => s(e.target.value),
220
293
  placeholder: "Beta Corp, John Roe, Gamma LLP"
221
294
  })
222
295
  }),
223
- /* @__PURE__ */ O(s, {
296
+ /* @__PURE__ */ I(c, {
224
297
  label: "Findings",
225
- children: /* @__PURE__ */ O(p, {
226
- value: u,
298
+ children: /* @__PURE__ */ I(g, {
299
+ value: l,
227
300
  onChange: (e) => d(e.target.value),
228
301
  placeholder: "Notes on any potential conflicts…"
229
302
  })
@@ -233,6 +306,6 @@ var j = () => {
233
306
  });
234
307
  };
235
308
  //#endregion
236
- export { j as ClientDetailPage };
309
+ export { B as ClientDetailPage };
237
310
 
238
311
  //# sourceMappingURL=ClientDetailPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ClientDetailPage.js","names":[],"sources":["../../../src/proserviceworld/pages/ClientDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n ArrowLeft,\n Briefcase,\n FolderKanban,\n GitBranch,\n Mail,\n Phone,\n MapPin,\n Plus,\n} from 'lucide-react';\nimport {\n useClient,\n useConflictChecks,\n useCreateConflictCheck,\n type PswEngagement,\n type PswMatter,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n Banner,\n fmtDate,\n fmtMoney,\n} from '../components/ui';\n\nexport const ClientDetailPage: FC = () => {\n const { id = '' } = useParams();\n const nav = useNav();\n const { data: client, isLoading, error, refetch } = useClient(id);\n const conflicts = useConflictChecks({ clientId: id });\n const [runConflict, setRunConflict] = useState(false);\n\n if (isLoading) return <LoadingBlock />;\n if (error) return <ErrorState error={error} onRetry={() => void refetch()} />;\n if (!client)\n return (\n <Empty\n title=\"Client not found\"\n description=\"It may have been archived or belongs to another workspace.\"\n />\n );\n\n return (\n <div className=\"space-y-6\">\n <button\n type=\"button\"\n onClick={() => nav('clients')}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary hover:text-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to clients\n </button>\n\n <PageHeader\n title={client.name}\n subtitle={[client.type === 'ORGANIZATION' ? 'Organization' : 'Individual', client.industry]\n .filter(Boolean)\n .join(' · ')}\n actions={<StatusPill status={client.status} />}\n />\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <SectionCard title=\"Contact\" className=\"lg:col-span-1\">\n <dl className=\"space-y-2 text-sm\">\n <Row label=\"Contact\" value={client.contactName} />\n {client.contactEmail && (\n <IconRow icon={<Mail className=\"h-3.5 w-3.5\" />} value={client.contactEmail} />\n )}\n {client.contactPhone && (\n <IconRow icon={<Phone className=\"h-3.5 w-3.5\" />} value={client.contactPhone} />\n )}\n {client.address && (\n <IconRow icon={<MapPin className=\"h-3.5 w-3.5\" />} value={client.address} />\n )}\n {client.notes && <p className=\"pt-2 text-text-secondary\">{client.notes}</p>}\n </dl>\n </SectionCard>\n\n <SectionCard\n title=\"Conflict checks\"\n className=\"lg:col-span-2\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setRunConflict(true)}>\n <GitBranch className=\"mr-1.5 h-4 w-4\" /> Run check\n </Button>\n }\n >\n {conflicts.isLoading ? (\n <LoadingBlock label=\"Loading…\" />\n ) : !conflicts.data || conflicts.data.length === 0 ? (\n <Empty\n title=\"No conflict checks\"\n description=\"Run a conflict check before accepting new work for this client.\"\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {conflicts.data.map((c) => (\n <li key={c.id} className=\"flex items-center justify-between py-2.5\">\n <div>\n <div className=\"text-sm text-text-primary\">\n {c.searchedParties.join(', ') || 'Conflict search'}\n </div>\n <div className=\"text-xs text-text-tertiary\">\n {fmtDate(c.createdAt)}\n {c.findings ? ` · ${c.findings}` : ''}\n </div>\n </div>\n <StatusPill status={c.status} />\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n </div>\n\n <SectionCard\n title=\"Engagements\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('engagements')}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Manage\n </Button>\n }\n >\n {client.engagements.length === 0 ? (\n <Empty\n title=\"No engagements\"\n description=\"Scope the first engagement for this client.\"\n icon={<Briefcase className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {client.engagements.map((e: PswEngagement) => (\n <li key={e.id}>\n <button\n type=\"button\"\n onClick={() => nav(`engagements/${e.id}`)}\n className=\"flex w-full items-center justify-between py-2.5 text-left hover:text-accent\"\n >\n <div>\n <div className=\"text-sm font-medium text-text-primary\">{e.name}</div>\n <div className=\"text-xs text-text-tertiary\">\n {fmtMoney(e.feeAmount, e.currency)} · {e.feeArrangement.toLowerCase()}\n </div>\n </div>\n <StatusPill status={e.status} />\n </button>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n <SectionCard\n title=\"Matters\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('matters')}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Manage\n </Button>\n }\n >\n {client.matters.length === 0 ? (\n <Empty\n title=\"No matters\"\n description=\"Open a matter to track regulated work for this client.\"\n icon={<FolderKanban className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {client.matters.map((m: PswMatter) => (\n <li key={m.id} className=\"flex items-center justify-between py-2.5\">\n <div>\n <div className=\"text-sm font-medium text-text-primary\">{m.title}</div>\n <div className=\"text-xs text-text-tertiary\">\n {m.reference || 'No reference'} · Due {fmtDate(m.dueDate)}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <StatusPill status={m.priority} />\n <StatusPill status={m.status} />\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n {runConflict && (\n <ConflictModal\n clientId={id}\n onClose={() => setRunConflict(false)}\n onDone={() => void conflicts.refetch()}\n />\n )}\n </div>\n );\n};\n\nconst Row: FC<{ label: string; value: string | null }> = ({ label, value }) =>\n value ? (\n <div className=\"flex justify-between gap-4\">\n <dt className=\"text-text-tertiary\">{label}</dt>\n <dd className=\"text-right text-text-primary\">{value}</dd>\n </div>\n ) : null;\n\nconst IconRow: FC<{ icon: React.ReactNode; value: string }> = ({ icon, value }) => (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <span className=\"text-text-tertiary\">{icon}</span>\n {value}\n </div>\n);\n\nconst ConflictModal: FC<{ clientId: string; onClose: () => void; onDone: () => void }> = ({\n clientId,\n onClose,\n onDone,\n}) => {\n const create = useCreateConflictCheck();\n const [parties, setParties] = useState('');\n const [findings, setFindings] = useState('');\n const [err, setErr] = useState<string | null>(null);\n\n const submit = () => {\n setErr(null);\n const searchedParties = parties\n .split(',')\n .map((p) => p.trim())\n .filter(Boolean);\n if (searchedParties.length === 0) return setErr('Add at least one party to search.');\n create.mutate(\n { clientId, searchedParties, findings: findings.trim() || null },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Run conflict check\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Running…' : 'Record check'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field\n label=\"Parties searched\"\n required\n hint=\"Comma-separated — counterparties, related entities, principals.\"\n >\n <TextInput\n value={parties}\n onChange={(e) => setParties(e.target.value)}\n placeholder=\"Beta Corp, John Roe, Gamma LLP\"\n />\n </Field>\n <Field label=\"Findings\">\n <TextArea\n value={findings}\n onChange={(e) => setFindings(e.target.value)}\n placeholder=\"Notes on any potential conflicts…\"\n />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ClientDetailPage.js","names":[],"sources":["../../../src/proserviceworld/pages/ClientDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n ArrowLeft,\n Briefcase,\n FolderKanban,\n GitBranch,\n Mail,\n Phone,\n MapPin,\n Plus,\n StickyNote,\n ChevronRight,\n} from 'lucide-react';\nimport {\n useClient,\n useConflictChecks,\n useCreateConflictCheck,\n type PswEngagement,\n type PswMatter,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport { cn } from '../utils/cn';\nimport { DiscussionThread } from '../components/DiscussionThread';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n Banner,\n TableWrap,\n Th,\n Td,\n fmtDate,\n fmtMoney,\n humanize,\n} from '../components/ui';\n\nconst initials = (name: string): string =>\n name\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 2)\n .map((w) => w[0]?.toUpperCase() ?? '')\n .join('') || '?';\n\nexport const ClientDetailPage: FC = () => {\n const { id = '' } = useParams();\n const nav = useNav();\n const { data: client, isLoading, error, refetch } = useClient(id);\n const conflicts = useConflictChecks({ clientId: id });\n const [runConflict, setRunConflict] = useState(false);\n\n if (isLoading) return <LoadingBlock />;\n if (error) return <ErrorState error={error} onRetry={() => void refetch()} />;\n if (!client)\n return (\n <Empty\n title=\"Client not found\"\n description=\"It may have been archived or belongs to another workspace.\"\n />\n );\n\n const avatarName = client.contactName || client.name;\n\n return (\n <div className=\"space-y-6\">\n <button\n type=\"button\"\n onClick={() => nav('clients')}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary transition-colors hover:text-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to clients\n </button>\n\n <PageHeader\n title={client.name}\n subtitle={[client.type === 'ORGANIZATION' ? 'Organization' : 'Individual', client.industry]\n .filter(Boolean)\n .join(' · ')}\n actions={<StatusPill status={client.status} />}\n />\n\n {client.tags.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {client.tags.map((t) => (\n <span\n key={t}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {t}\n </span>\n ))}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <SectionCard title=\"Contact\" className=\"lg:col-span-1\">\n <div className=\"space-y-4\">\n <div className=\"flex items-center gap-3\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-accent/10 text-sm font-semibold text-accent ring-1 ring-inset ring-accent/15\">\n {initials(avatarName)}\n </span>\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-semibold text-text-primary\">\n {client.contactName || client.name}\n </div>\n <div className=\"truncate text-xs text-text-tertiary\">\n {client.contactName ? 'Primary contact' : 'Client record'}\n </div>\n </div>\n </div>\n\n <dl className=\"space-y-2.5 text-sm\">\n {client.contactEmail && (\n <IconRow icon={<Mail className=\"h-4 w-4\" />} value={client.contactEmail} />\n )}\n {client.contactPhone && (\n <IconRow icon={<Phone className=\"h-4 w-4\" />} value={client.contactPhone} />\n )}\n {client.address && (\n <IconRow icon={<MapPin className=\"h-4 w-4\" />} value={client.address} />\n )}\n {client.notes && (\n <IconRow icon={<StickyNote className=\"h-4 w-4\" />} value={client.notes} multiline />\n )}\n {!client.contactEmail && !client.contactPhone && !client.address && !client.notes && (\n <p className=\"text-sm text-text-tertiary\">No contact details recorded yet.</p>\n )}\n </dl>\n </div>\n </SectionCard>\n\n <SectionCard\n title=\"Conflict checks\"\n className=\"lg:col-span-2\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setRunConflict(true)}>\n <GitBranch className=\"mr-1.5 h-4 w-4\" /> Run check\n </Button>\n }\n >\n {conflicts.isLoading ? (\n <LoadingBlock label=\"Loading…\" />\n ) : !conflicts.data || conflicts.data.length === 0 ? (\n <Empty\n title=\"No conflict checks\"\n description=\"Run a conflict check before accepting new work for this client.\"\n icon={<GitBranch className=\"h-7 w-7\" />}\n />\n ) : (\n <TableWrap>\n <thead>\n <tr>\n <Th>Parties searched</Th>\n <Th>Date</Th>\n <Th className=\"text-right\">Status</Th>\n </tr>\n </thead>\n <tbody>\n {conflicts.data.map((c) => (\n <tr key={c.id} className=\"transition-colors hover:bg-bg-sunken/60\">\n <Td>\n <div className=\"font-medium text-text-primary\">\n {c.searchedParties.join(', ') || 'Conflict search'}\n </div>\n {c.findings ? (\n <div className=\"text-xs text-text-tertiary\">{c.findings}</div>\n ) : null}\n </Td>\n <Td className=\"whitespace-nowrap\">{fmtDate(c.createdAt)}</Td>\n <Td className=\"text-right\">\n <StatusPill status={c.status} />\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n )}\n </SectionCard>\n </div>\n\n <SectionCard\n title=\"Engagements\"\n description={`${client.engagements.length} scoped for this client`}\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('engagements')}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Manage\n </Button>\n }\n >\n {client.engagements.length === 0 ? (\n <Empty\n title=\"No engagements\"\n description=\"Scope the first engagement for this client.\"\n icon={<Briefcase className=\"h-7 w-7\" />}\n />\n ) : (\n <TableWrap>\n <thead>\n <tr>\n <Th>Engagement</Th>\n <Th>Fee</Th>\n <Th>Started</Th>\n <Th className=\"text-right\">Status</Th>\n </tr>\n </thead>\n <tbody>\n {client.engagements.map((e: PswEngagement) => (\n <tr\n key={e.id}\n className=\"group cursor-pointer transition-colors hover:bg-bg-sunken/60\"\n onClick={() => nav(`engagements/${e.id}`)}\n >\n <Td>\n <div className=\"flex items-center gap-1 font-medium text-text-primary transition-colors group-hover:text-accent\">\n <span>{e.name}</span>\n <ChevronRight className=\"h-3.5 w-3.5 shrink-0 text-text-tertiary opacity-0 transition-opacity group-hover:opacity-100\" />\n </div>\n </Td>\n <Td className=\"whitespace-nowrap\">\n {fmtMoney(e.feeAmount, e.currency)}{' '}\n <span className=\"text-xs text-text-tertiary\">\n / {humanize(e.feeArrangement)}\n </span>\n </Td>\n <Td className=\"whitespace-nowrap\">{fmtDate(e.startDate)}</Td>\n <Td className=\"text-right\">\n <StatusPill status={e.status} />\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n )}\n </SectionCard>\n\n <SectionCard\n title=\"Matters\"\n description={`${client.matters.length} tracked for this client`}\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('matters')}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Manage\n </Button>\n }\n >\n {client.matters.length === 0 ? (\n <Empty\n title=\"No matters\"\n description=\"Open a matter to track regulated work for this client.\"\n icon={<FolderKanban className=\"h-7 w-7\" />}\n />\n ) : (\n <TableWrap>\n <thead>\n <tr>\n <Th>Reference</Th>\n <Th>Title</Th>\n <Th>Due</Th>\n <Th>Priority</Th>\n <Th className=\"text-right\">Status</Th>\n </tr>\n </thead>\n <tbody>\n {client.matters.map((m: PswMatter) => (\n <tr key={m.id} className=\"transition-colors hover:bg-bg-sunken/60\">\n <Td className=\"whitespace-nowrap font-medium text-text-primary\">\n {m.reference || '—'}\n </Td>\n <Td>{m.title}</Td>\n <Td className=\"whitespace-nowrap\">{fmtDate(m.dueDate)}</Td>\n <Td>\n <StatusPill status={m.priority} />\n </Td>\n <Td className=\"text-right\">\n <StatusPill status={m.status} />\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n )}\n </SectionCard>\n\n <DiscussionThread entityType=\"proserviceworld-client\" entityId={id} />\n\n {runConflict && (\n <ConflictModal\n clientId={id}\n onClose={() => setRunConflict(false)}\n onDone={() => void conflicts.refetch()}\n />\n )}\n </div>\n );\n};\n\nconst IconRow: FC<{ icon: React.ReactNode; value: string; multiline?: boolean }> = ({\n icon,\n value,\n multiline,\n}) => (\n <div\n className={cn('flex gap-2.5 text-text-secondary', multiline ? 'items-start' : 'items-center')}\n >\n <span className=\"mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-bg-sunken text-text-tertiary\">\n {icon}\n </span>\n <span className={cn('min-w-0 break-words', multiline ? 'leading-relaxed' : 'truncate')}>\n {value}\n </span>\n </div>\n);\n\nconst ConflictModal: FC<{ clientId: string; onClose: () => void; onDone: () => void }> = ({\n clientId,\n onClose,\n onDone,\n}) => {\n const create = useCreateConflictCheck();\n const [parties, setParties] = useState('');\n const [findings, setFindings] = useState('');\n const [err, setErr] = useState<string | null>(null);\n\n const submit = () => {\n setErr(null);\n const searchedParties = parties\n .split(',')\n .map((p) => p.trim())\n .filter(Boolean);\n if (searchedParties.length === 0) return setErr('Add at least one party to search.');\n create.mutate(\n { clientId, searchedParties, findings: findings.trim() || null },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Run conflict check\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Running…' : 'Record check'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field\n label=\"Parties searched\"\n required\n hint=\"Comma-separated — counterparties, related entities, principals.\"\n >\n <TextInput\n value={parties}\n onChange={(e) => setParties(e.target.value)}\n placeholder=\"Beta Corp, John Roe, Gamma LLP\"\n />\n </Field>\n <Field label=\"Findings\">\n <TextArea\n value={findings}\n onChange={(e) => setFindings(e.target.value)}\n placeholder=\"Notes on any potential conflicts…\"\n />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}