@burdenoff/microfe-proserviceworld 2026.709.1 → 2026.709.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.
- package/dist/index.js +2 -1
- package/dist/node_modules/clsx/dist/clsx.js +1 -1
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +130 -130
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
- package/dist/proserviceworld/ProServiceWorldRoot.js +4 -2
- package/dist/proserviceworld/ProServiceWorldRoot.js.map +1 -1
- package/dist/proserviceworld/ProServiceWorldRoutes.d.ts.map +1 -1
- package/dist/proserviceworld/ProServiceWorldRoutes.js +95 -42
- package/dist/proserviceworld/ProServiceWorldRoutes.js.map +1 -1
- package/dist/proserviceworld/components/DiscussionThread.js.map +1 -1
- package/dist/proserviceworld/components/FeatureFlaggedPage.d.ts +8 -0
- package/dist/proserviceworld/components/FeatureFlaggedPage.d.ts.map +1 -0
- package/dist/proserviceworld/components/FeatureFlaggedPage.js +17 -0
- package/dist/proserviceworld/components/FeatureFlaggedPage.js.map +1 -0
- package/dist/proserviceworld/components/LoadMore.d.ts +12 -0
- package/dist/proserviceworld/components/LoadMore.d.ts.map +1 -0
- package/dist/proserviceworld/components/LoadMore.js +30 -0
- package/dist/proserviceworld/components/LoadMore.js.map +1 -0
- package/dist/proserviceworld/components/TagPicker.d.ts +11 -0
- package/dist/proserviceworld/components/TagPicker.d.ts.map +1 -0
- package/dist/proserviceworld/components/TagPicker.js +31 -0
- package/dist/proserviceworld/components/TagPicker.js.map +1 -0
- package/dist/proserviceworld/components/ui.js.map +1 -1
- package/dist/proserviceworld/hooks/useAnalytics.js.map +1 -1
- package/dist/proserviceworld/hooks/useEntityComments.js.map +1 -1
- package/dist/proserviceworld/hooks/useFiles.js.map +1 -1
- package/dist/proserviceworld/hooks/usePageViewAnalytics.d.ts +10 -0
- package/dist/proserviceworld/hooks/usePageViewAnalytics.d.ts.map +1 -0
- package/dist/proserviceworld/hooks/usePageViewAnalytics.js +24 -0
- package/dist/proserviceworld/hooks/usePageViewAnalytics.js.map +1 -0
- package/dist/proserviceworld/hooks/useProServiceWorldApi.js +47 -27
- package/dist/proserviceworld/hooks/useProServiceWorldApi.js.map +1 -1
- package/dist/proserviceworld/i18n/index.d.ts +13 -0
- package/dist/proserviceworld/i18n/index.d.ts.map +1 -0
- package/dist/proserviceworld/i18n/index.js +171 -0
- package/dist/proserviceworld/i18n/index.js.map +1 -0
- package/dist/proserviceworld/index.d.ts +1 -0
- package/dist/proserviceworld/index.d.ts.map +1 -1
- package/dist/proserviceworld/index.js +5 -3
- package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ClientDetailPage.js.map +1 -1
- package/dist/proserviceworld/pages/ClientsPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/ClientsPage.js +173 -121
- package/dist/proserviceworld/pages/ClientsPage.js.map +1 -1
- package/dist/proserviceworld/pages/CompliancePage.js.map +1 -1
- package/dist/proserviceworld/pages/ConflictsPage.js.map +1 -1
- package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
- package/dist/proserviceworld/pages/DocumentsPage.js.map +1 -1
- package/dist/proserviceworld/pages/EngagementDetailPage.js.map +1 -1
- package/dist/proserviceworld/pages/EngagementsPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/EngagementsPage.js +168 -120
- package/dist/proserviceworld/pages/EngagementsPage.js.map +1 -1
- package/dist/proserviceworld/pages/InvoicesPage.js.map +1 -1
- package/dist/proserviceworld/pages/MattersPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/MattersPage.js +183 -144
- package/dist/proserviceworld/pages/MattersPage.js.map +1 -1
- package/dist/proserviceworld/pages/PracticeAreasPage.js.map +1 -1
- package/dist/proserviceworld/pages/TimePage.js.map +1 -1
- package/dist/proserviceworld/providers/ProServiceWorldProvider.js.map +1 -1
- package/dist/proserviceworld/services/assistantApi.js.map +1 -1
- package/dist/proserviceworld/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/proserviceworld/utils/cn.js +1 -1
- package/dist/proserviceworld/utils/cn.js.map +1 -1
- package/dist/proserviceworld/utils/native.d.ts +23 -0
- package/dist/proserviceworld/utils/native.d.ts.map +1 -0
- package/dist/proserviceworld/utils/native.js +14 -0
- package/dist/proserviceworld/utils/native.js.map +1 -0
- package/dist/proserviceworld/utils/nav.js.map +1 -1
- package/dist/proserviceworld/utils/navigation.js.map +1 -1
- package/dist/proserviceworld/utils/pageContext.js +14 -16
- package/dist/proserviceworld/utils/pageContext.js.map +1 -1
- package/package.json +2 -2
|
@@ -2,22 +2,26 @@ import { useArchiveClient as e, useClients as t, useCreateClient as n, useUpdate
|
|
|
2
2
|
import { useNav as i } from "../utils/nav.js";
|
|
3
3
|
import { cn as a } from "../utils/cn.js";
|
|
4
4
|
import { Banner as o, Empty as s, ErrorState as c, Field as l, LoadingBlock as u, Modal as d, NativeSelect as f, PageHeader as p, SectionCard as m, StatusPill as h, TableWrap as g, Td as _, TextArea as v, TextInput as y, Th as b, enumOptions as x, fmtDate as S } from "../components/ui.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
5
|
+
import { t as C } from "../i18n/index.js";
|
|
6
|
+
import { useAnalytics as w } from "../hooks/useAnalytics.js";
|
|
7
|
+
import { impactFeedback as T } from "../utils/native.js";
|
|
8
|
+
import { TagPicker as E } from "../components/TagPicker.js";
|
|
9
|
+
import { LoadMore as D } from "../components/LoadMore.js";
|
|
10
|
+
import { useMemo as O, useState as k } from "react";
|
|
11
|
+
import { Archive as A, Building2 as ee, ChevronRight as te, Pencil as ne, Plus as j, Search as M, User as N, Users as P } from "lucide-react";
|
|
12
|
+
import { Fragment as F, jsx as I, jsxs as L } from "react/jsx-runtime";
|
|
13
|
+
import { Button as R } from "@burdenoff/fe-libs/ui";
|
|
10
14
|
//#region src/proserviceworld/pages/ClientsPage.tsx
|
|
11
|
-
var
|
|
15
|
+
var z = ["ORGANIZATION", "INDIVIDUAL"], B = [
|
|
12
16
|
"PROSPECT",
|
|
13
17
|
"ACTIVE",
|
|
14
18
|
"INACTIVE",
|
|
15
19
|
"ARCHIVED"
|
|
16
|
-
],
|
|
20
|
+
], V = (e) => e.split(/\s+/).filter(Boolean).slice(0, 2).map((e) => e[0]?.toUpperCase() ?? "").join("") || "?", H = ({ name: e, className: t }) => /* @__PURE__ */ I("span", {
|
|
17
21
|
className: a("flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent ring-1 ring-inset ring-accent/15", t),
|
|
18
22
|
"aria-hidden": !0,
|
|
19
|
-
children:
|
|
20
|
-
}),
|
|
23
|
+
children: V(e)
|
|
24
|
+
}), U = {
|
|
21
25
|
name: "",
|
|
22
26
|
type: "ORGANIZATION",
|
|
23
27
|
status: "PROSPECT",
|
|
@@ -26,124 +30,163 @@ var R = ["ORGANIZATION", "INDIVIDUAL"], z = [
|
|
|
26
30
|
contactEmail: "",
|
|
27
31
|
contactPhone: "",
|
|
28
32
|
notes: ""
|
|
29
|
-
},
|
|
30
|
-
let e = i(), { emitIntent: n, emitFact:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
})
|
|
34
|
-
|
|
33
|
+
}, W = 25, G = () => {
|
|
34
|
+
let e = i(), { emitIntent: n, emitFact: a } = w(), [l, d] = k(""), [v, A] = k(""), [F, z] = k(null), [V, U] = k(!1), [G, q] = k(null), [ie, J] = k({
|
|
35
|
+
limit: W,
|
|
36
|
+
offset: 0
|
|
37
|
+
}), { data: Y, isLoading: X, error: Z, refetch: Q } = t({
|
|
38
|
+
search: l.trim() || void 0,
|
|
39
|
+
status: v || void 0,
|
|
40
|
+
pagination: ie
|
|
41
|
+
}), ae = r(), $ = O(() => Y?.items ?? [], [Y]);
|
|
42
|
+
return /* @__PURE__ */ L("div", {
|
|
35
43
|
className: "space-y-6",
|
|
36
44
|
children: [
|
|
37
|
-
/* @__PURE__ */
|
|
38
|
-
title: "
|
|
39
|
-
subtitle: "
|
|
40
|
-
icon: /* @__PURE__ */
|
|
41
|
-
actions: /* @__PURE__ */
|
|
45
|
+
/* @__PURE__ */ I(p, {
|
|
46
|
+
title: C("psw.clients.title"),
|
|
47
|
+
subtitle: C("psw.clients.subtitle"),
|
|
48
|
+
icon: /* @__PURE__ */ I(P, { className: "h-6 w-6" }),
|
|
49
|
+
actions: /* @__PURE__ */ L(R, {
|
|
42
50
|
onClick: () => {
|
|
43
|
-
n("psw.client.create_clicked"),
|
|
51
|
+
T("light"), n("psw.client.create_clicked"), U(!0);
|
|
44
52
|
},
|
|
45
|
-
children: [
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ I(j, { className: "mr-1.5 h-4 w-4" }),
|
|
55
|
+
" ",
|
|
56
|
+
C("psw.clients.new")
|
|
57
|
+
]
|
|
46
58
|
})
|
|
47
59
|
}),
|
|
48
|
-
|
|
49
|
-
tone:
|
|
50
|
-
onClose: () =>
|
|
51
|
-
children:
|
|
60
|
+
G && /* @__PURE__ */ I(o, {
|
|
61
|
+
tone: G.tone,
|
|
62
|
+
onClose: () => q(null),
|
|
63
|
+
children: G.text
|
|
52
64
|
}),
|
|
53
|
-
/* @__PURE__ */
|
|
65
|
+
/* @__PURE__ */ L("div", {
|
|
54
66
|
className: "flex flex-col gap-2 sm:flex-row sm:items-center",
|
|
55
|
-
children: [/* @__PURE__ */
|
|
67
|
+
children: [/* @__PURE__ */ L("div", {
|
|
56
68
|
className: "relative flex-1",
|
|
57
|
-
children: [/* @__PURE__ */
|
|
58
|
-
placeholder: "
|
|
59
|
-
value:
|
|
60
|
-
onChange: (e) =>
|
|
69
|
+
children: [/* @__PURE__ */ I(M, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary" }), /* @__PURE__ */ I(y, {
|
|
70
|
+
placeholder: C("psw.clients.searchPlaceholder"),
|
|
71
|
+
value: l,
|
|
72
|
+
onChange: (e) => {
|
|
73
|
+
d(e.target.value), J({
|
|
74
|
+
limit: W,
|
|
75
|
+
offset: 0
|
|
76
|
+
});
|
|
77
|
+
},
|
|
61
78
|
onKeyDown: (e) => {
|
|
62
|
-
e.key === "Enter" &&
|
|
79
|
+
e.key === "Enter" && l.trim() && a("psw.search.performed", {
|
|
63
80
|
entityType: "client",
|
|
64
|
-
queryLength:
|
|
81
|
+
queryLength: l.trim().length
|
|
65
82
|
});
|
|
66
83
|
},
|
|
67
84
|
className: "pl-9"
|
|
68
85
|
})]
|
|
69
|
-
}), /* @__PURE__ */
|
|
70
|
-
value:
|
|
71
|
-
onChange: (e) =>
|
|
86
|
+
}), /* @__PURE__ */ L(f, {
|
|
87
|
+
value: v,
|
|
88
|
+
onChange: (e) => {
|
|
89
|
+
A(e.target.value), J({
|
|
90
|
+
limit: W,
|
|
91
|
+
offset: 0
|
|
92
|
+
});
|
|
93
|
+
},
|
|
72
94
|
className: "sm:w-48",
|
|
73
|
-
children: [/* @__PURE__ */
|
|
95
|
+
children: [/* @__PURE__ */ I("option", {
|
|
74
96
|
value: "",
|
|
75
|
-
children: "
|
|
76
|
-
}), x(
|
|
97
|
+
children: C("psw.clients.allStatuses")
|
|
98
|
+
}), x(B)]
|
|
77
99
|
})]
|
|
78
100
|
}),
|
|
79
|
-
|
|
80
|
-
error:
|
|
81
|
-
onRetry: () => void
|
|
82
|
-
}) : !
|
|
83
|
-
title: "
|
|
84
|
-
description: "
|
|
85
|
-
icon: /* @__PURE__ */
|
|
86
|
-
action: /* @__PURE__ */
|
|
87
|
-
onClick: () =>
|
|
88
|
-
children: [
|
|
101
|
+
X ? /* @__PURE__ */ I(u, {}) : Z ? /* @__PURE__ */ I(c, {
|
|
102
|
+
error: Z,
|
|
103
|
+
onRetry: () => void Q()
|
|
104
|
+
}) : !Y || Y.items.length === 0 ? /* @__PURE__ */ I(s, {
|
|
105
|
+
title: C("psw.clients.emptyTitle"),
|
|
106
|
+
description: C("psw.clients.emptyDescription"),
|
|
107
|
+
icon: /* @__PURE__ */ I(P, { className: "h-8 w-8" }),
|
|
108
|
+
action: /* @__PURE__ */ L(R, {
|
|
109
|
+
onClick: () => U(!0),
|
|
110
|
+
children: [
|
|
111
|
+
/* @__PURE__ */ I(j, { className: "mr-1.5 h-4 w-4" }),
|
|
112
|
+
" ",
|
|
113
|
+
C("psw.clients.new")
|
|
114
|
+
]
|
|
89
115
|
})
|
|
90
|
-
}) : /* @__PURE__ */
|
|
91
|
-
title: `${
|
|
92
|
-
children: /* @__PURE__ */
|
|
93
|
-
/* @__PURE__ */
|
|
94
|
-
/* @__PURE__ */
|
|
95
|
-
/* @__PURE__ */
|
|
96
|
-
/* @__PURE__ */
|
|
97
|
-
/* @__PURE__ */
|
|
98
|
-
/* @__PURE__ */
|
|
116
|
+
}) : /* @__PURE__ */ L(m, {
|
|
117
|
+
title: `${Y.totalCount} client${Y.totalCount === 1 ? "" : "s"}`,
|
|
118
|
+
children: [/* @__PURE__ */ L(g, { children: [/* @__PURE__ */ I("thead", { children: /* @__PURE__ */ L("tr", { children: [
|
|
119
|
+
/* @__PURE__ */ I(b, { children: C("psw.common.name") }),
|
|
120
|
+
/* @__PURE__ */ I(b, { children: C("psw.common.type") }),
|
|
121
|
+
/* @__PURE__ */ I(b, { children: "Industry" }),
|
|
122
|
+
/* @__PURE__ */ I(b, { children: C("psw.common.status") }),
|
|
123
|
+
/* @__PURE__ */ I(b, { children: C("psw.common.added") }),
|
|
124
|
+
/* @__PURE__ */ I(b, { children: "Tags" }),
|
|
125
|
+
/* @__PURE__ */ I(b, {
|
|
99
126
|
className: "text-right",
|
|
100
|
-
children: "
|
|
127
|
+
children: C("psw.common.actions")
|
|
101
128
|
})
|
|
102
|
-
] }) }), /* @__PURE__ */
|
|
129
|
+
] }) }), /* @__PURE__ */ I("tbody", { children: Y.items.map((t) => /* @__PURE__ */ L("tr", {
|
|
103
130
|
className: "group cursor-pointer transition-colors hover:bg-bg-sunken/60",
|
|
104
131
|
onClick: () => e(`clients/${t.id}`),
|
|
105
132
|
children: [
|
|
106
|
-
/* @__PURE__ */
|
|
133
|
+
/* @__PURE__ */ I(_, { children: /* @__PURE__ */ L("div", {
|
|
107
134
|
className: "flex items-center gap-3",
|
|
108
|
-
children: [/* @__PURE__ */
|
|
135
|
+
children: [/* @__PURE__ */ I(H, { name: t.name }), /* @__PURE__ */ L("div", {
|
|
109
136
|
className: "min-w-0",
|
|
110
|
-
children: [/* @__PURE__ */
|
|
137
|
+
children: [/* @__PURE__ */ L("div", {
|
|
111
138
|
className: "flex items-center gap-1 font-medium text-text-primary transition-colors group-hover:text-accent",
|
|
112
|
-
children: [/* @__PURE__ */
|
|
139
|
+
children: [/* @__PURE__ */ I("span", {
|
|
113
140
|
className: "truncate",
|
|
114
141
|
children: t.name
|
|
115
|
-
}), /* @__PURE__ */
|
|
116
|
-
}), t.contactName ? /* @__PURE__ */
|
|
142
|
+
}), /* @__PURE__ */ I(te, { className: "h-3.5 w-3.5 shrink-0 text-text-tertiary opacity-0 transition-opacity group-hover:opacity-100" })]
|
|
143
|
+
}), t.contactName ? /* @__PURE__ */ I("div", {
|
|
117
144
|
className: "truncate text-xs text-text-tertiary",
|
|
118
145
|
children: t.contactName
|
|
119
146
|
}) : null]
|
|
120
147
|
})]
|
|
121
148
|
}) }),
|
|
122
|
-
/* @__PURE__ */
|
|
149
|
+
/* @__PURE__ */ I(_, { children: /* @__PURE__ */ L("span", {
|
|
123
150
|
className: "inline-flex items-center gap-1.5 text-text-secondary",
|
|
124
|
-
children: [t.type === "ORGANIZATION" ? /* @__PURE__ */
|
|
151
|
+
children: [t.type === "ORGANIZATION" ? /* @__PURE__ */ I(ee, { className: "h-3.5 w-3.5 text-text-tertiary" }) : /* @__PURE__ */ I(N, { className: "h-3.5 w-3.5 text-text-tertiary" }), t.type === "ORGANIZATION" ? "Organization" : "Individual"]
|
|
125
152
|
}) }),
|
|
126
|
-
/* @__PURE__ */
|
|
127
|
-
/* @__PURE__ */
|
|
128
|
-
/* @__PURE__ */
|
|
153
|
+
/* @__PURE__ */ I(_, { children: t.industry || "—" }),
|
|
154
|
+
/* @__PURE__ */ I(_, { children: /* @__PURE__ */ I(h, { status: t.status }) }),
|
|
155
|
+
/* @__PURE__ */ I(_, {
|
|
129
156
|
className: "whitespace-nowrap",
|
|
130
157
|
children: S(t.createdAt)
|
|
131
158
|
}),
|
|
132
|
-
/* @__PURE__ */
|
|
159
|
+
/* @__PURE__ */ I(_, { children: /* @__PURE__ */ I("div", {
|
|
160
|
+
onClick: (e) => e.stopPropagation(),
|
|
161
|
+
role: "presentation",
|
|
162
|
+
children: /* @__PURE__ */ I(E, {
|
|
163
|
+
entityType: "PswClient",
|
|
164
|
+
entityId: t.id,
|
|
165
|
+
tags: t.tags ?? [],
|
|
166
|
+
onChange: async (e) => {
|
|
167
|
+
await ae.mutateAsync({
|
|
168
|
+
id: t.id,
|
|
169
|
+
input: { tags: e }
|
|
170
|
+
}), Q();
|
|
171
|
+
},
|
|
172
|
+
maxTags: 5
|
|
173
|
+
})
|
|
174
|
+
}) }),
|
|
175
|
+
/* @__PURE__ */ I(_, {
|
|
133
176
|
className: "text-right",
|
|
134
|
-
children: /* @__PURE__ */
|
|
177
|
+
children: /* @__PURE__ */ L("div", {
|
|
135
178
|
className: "flex justify-end gap-1",
|
|
136
179
|
onClick: (e) => e.stopPropagation(),
|
|
137
180
|
role: "presentation",
|
|
138
|
-
children: [/* @__PURE__ */
|
|
181
|
+
children: [/* @__PURE__ */ I(R, {
|
|
139
182
|
variant: "ghost",
|
|
140
183
|
size: "sm",
|
|
141
|
-
onClick: () =>
|
|
184
|
+
onClick: () => z(t),
|
|
142
185
|
"aria-label": "Edit",
|
|
143
|
-
children: /* @__PURE__ */
|
|
144
|
-
}), t.status !== "ARCHIVED" && /* @__PURE__ */
|
|
186
|
+
children: /* @__PURE__ */ I(ne, { className: "h-4 w-4" })
|
|
187
|
+
}), t.status !== "ARCHIVED" && /* @__PURE__ */ I(re, {
|
|
145
188
|
id: t.id,
|
|
146
|
-
onDone: (e) =>
|
|
189
|
+
onDone: (e) => q(e ? {
|
|
147
190
|
tone: "success",
|
|
148
191
|
text: `${t.name} archived.`
|
|
149
192
|
} : {
|
|
@@ -154,28 +197,37 @@ var R = ["ORGANIZATION", "INDIVIDUAL"], z = [
|
|
|
154
197
|
})
|
|
155
198
|
})
|
|
156
199
|
]
|
|
157
|
-
}, t.id)) })] })
|
|
200
|
+
}, t.id)) })] }), /* @__PURE__ */ I(D, {
|
|
201
|
+
hasMore: Y?.pageInfo?.hasMore ?? !1,
|
|
202
|
+
loading: X,
|
|
203
|
+
loadedCount: $.length,
|
|
204
|
+
totalCount: Y?.totalCount ?? $.length,
|
|
205
|
+
onLoadMore: () => J((e) => ({
|
|
206
|
+
limit: W,
|
|
207
|
+
offset: e.offset + W
|
|
208
|
+
}))
|
|
209
|
+
})]
|
|
158
210
|
}),
|
|
159
|
-
|
|
160
|
-
onClose: () =>
|
|
161
|
-
onSaved: (e) =>
|
|
211
|
+
V && /* @__PURE__ */ I(K, {
|
|
212
|
+
onClose: () => U(!1),
|
|
213
|
+
onSaved: (e) => q({
|
|
162
214
|
tone: "success",
|
|
163
215
|
text: `${e} created.`
|
|
164
216
|
})
|
|
165
217
|
}),
|
|
166
|
-
|
|
167
|
-
client:
|
|
168
|
-
onClose: () =>
|
|
169
|
-
onSaved: (e) =>
|
|
218
|
+
F && /* @__PURE__ */ I(K, {
|
|
219
|
+
client: F,
|
|
220
|
+
onClose: () => z(null),
|
|
221
|
+
onSaved: (e) => q({
|
|
170
222
|
tone: "success",
|
|
171
223
|
text: `${e} updated.`
|
|
172
224
|
})
|
|
173
225
|
})
|
|
174
226
|
]
|
|
175
227
|
});
|
|
176
|
-
},
|
|
177
|
-
let r = e(), { emitFact: i } =
|
|
178
|
-
return /* @__PURE__ */
|
|
228
|
+
}, re = ({ id: t, onDone: n }) => {
|
|
229
|
+
let r = e(), { emitFact: i } = w();
|
|
230
|
+
return /* @__PURE__ */ I(R, {
|
|
179
231
|
variant: "ghost",
|
|
180
232
|
size: "sm",
|
|
181
233
|
"aria-label": "Archive",
|
|
@@ -188,10 +240,10 @@ var R = ["ORGANIZATION", "INDIVIDUAL"], z = [
|
|
|
188
240
|
onError: () => n(!1)
|
|
189
241
|
});
|
|
190
242
|
},
|
|
191
|
-
children: /* @__PURE__ */
|
|
243
|
+
children: /* @__PURE__ */ I(A, { className: "h-4 w-4" })
|
|
192
244
|
});
|
|
193
|
-
},
|
|
194
|
-
let a = n(), s = r(), { emitFact: c } =
|
|
245
|
+
}, K = ({ client: e, onClose: t, onSaved: i }) => {
|
|
246
|
+
let a = n(), s = r(), { emitFact: c } = w(), [u, p] = k(e ? {
|
|
195
247
|
name: e.name,
|
|
196
248
|
type: e.type,
|
|
197
249
|
status: e.status,
|
|
@@ -200,10 +252,10 @@ var R = ["ORGANIZATION", "INDIVIDUAL"], z = [
|
|
|
200
252
|
contactEmail: e.contactEmail ?? "",
|
|
201
253
|
contactPhone: e.contactPhone ?? "",
|
|
202
254
|
notes: e.notes ?? ""
|
|
203
|
-
} :
|
|
255
|
+
} : U), [m, h] = k(null), g = (e) => (t) => p((n) => ({
|
|
204
256
|
...n,
|
|
205
257
|
[e]: t.target.value
|
|
206
|
-
})), _ = a.isPending || s.isPending, b =
|
|
258
|
+
})), _ = a.isPending || s.isPending, b = O(() => ({
|
|
207
259
|
name: u.name.trim(),
|
|
208
260
|
type: u.type,
|
|
209
261
|
status: u.status,
|
|
@@ -213,16 +265,16 @@ var R = ["ORGANIZATION", "INDIVIDUAL"], z = [
|
|
|
213
265
|
contactPhone: u.contactPhone.trim() || null,
|
|
214
266
|
notes: u.notes.trim() || null
|
|
215
267
|
}), [u]);
|
|
216
|
-
return /* @__PURE__ */
|
|
268
|
+
return /* @__PURE__ */ I(d, {
|
|
217
269
|
open: !0,
|
|
218
270
|
onClose: t,
|
|
219
271
|
title: e ? "Edit client" : "New client",
|
|
220
|
-
footer: /* @__PURE__ */
|
|
272
|
+
footer: /* @__PURE__ */ L(F, { children: [/* @__PURE__ */ I(R, {
|
|
221
273
|
variant: "outline",
|
|
222
274
|
onClick: t,
|
|
223
275
|
disabled: _,
|
|
224
276
|
children: "Cancel"
|
|
225
|
-
}), /* @__PURE__ */
|
|
277
|
+
}), /* @__PURE__ */ I(R, {
|
|
226
278
|
onClick: () => {
|
|
227
279
|
if (h(null), !b.name) return h("Client name is required.");
|
|
228
280
|
let n = (e) => h(e instanceof Error ? e.message : "Save failed.");
|
|
@@ -244,75 +296,75 @@ var R = ["ORGANIZATION", "INDIVIDUAL"], z = [
|
|
|
244
296
|
disabled: _,
|
|
245
297
|
children: _ ? "Saving…" : e ? "Save changes" : "Create client"
|
|
246
298
|
})] }),
|
|
247
|
-
children: /* @__PURE__ */
|
|
299
|
+
children: /* @__PURE__ */ L("div", {
|
|
248
300
|
className: "space-y-3",
|
|
249
301
|
children: [
|
|
250
|
-
m && /* @__PURE__ */
|
|
302
|
+
m && /* @__PURE__ */ I(o, {
|
|
251
303
|
tone: "danger",
|
|
252
304
|
children: m
|
|
253
305
|
}),
|
|
254
|
-
/* @__PURE__ */
|
|
306
|
+
/* @__PURE__ */ I(l, {
|
|
255
307
|
label: "Name",
|
|
256
308
|
required: !0,
|
|
257
|
-
children: /* @__PURE__ */
|
|
309
|
+
children: /* @__PURE__ */ I(y, {
|
|
258
310
|
value: u.name,
|
|
259
311
|
onChange: g("name"),
|
|
260
312
|
placeholder: "Acme Holdings Ltd."
|
|
261
313
|
})
|
|
262
314
|
}),
|
|
263
|
-
/* @__PURE__ */
|
|
315
|
+
/* @__PURE__ */ L("div", {
|
|
264
316
|
className: "grid grid-cols-2 gap-3",
|
|
265
|
-
children: [/* @__PURE__ */
|
|
317
|
+
children: [/* @__PURE__ */ I(l, {
|
|
266
318
|
label: "Type",
|
|
267
|
-
children: /* @__PURE__ */
|
|
319
|
+
children: /* @__PURE__ */ I(f, {
|
|
268
320
|
value: u.type,
|
|
269
321
|
onChange: g("type"),
|
|
270
|
-
children: x(
|
|
322
|
+
children: x(z)
|
|
271
323
|
})
|
|
272
|
-
}), /* @__PURE__ */
|
|
324
|
+
}), /* @__PURE__ */ I(l, {
|
|
273
325
|
label: "Status",
|
|
274
|
-
children: /* @__PURE__ */
|
|
326
|
+
children: /* @__PURE__ */ I(f, {
|
|
275
327
|
value: u.status,
|
|
276
328
|
onChange: g("status"),
|
|
277
|
-
children: x(
|
|
329
|
+
children: x(B)
|
|
278
330
|
})
|
|
279
331
|
})]
|
|
280
332
|
}),
|
|
281
|
-
/* @__PURE__ */
|
|
333
|
+
/* @__PURE__ */ I(l, {
|
|
282
334
|
label: "Industry",
|
|
283
|
-
children: /* @__PURE__ */
|
|
335
|
+
children: /* @__PURE__ */ I(y, {
|
|
284
336
|
value: u.industry,
|
|
285
337
|
onChange: g("industry"),
|
|
286
338
|
placeholder: "Financial services"
|
|
287
339
|
})
|
|
288
340
|
}),
|
|
289
|
-
/* @__PURE__ */
|
|
341
|
+
/* @__PURE__ */ L("div", {
|
|
290
342
|
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
291
|
-
children: [/* @__PURE__ */
|
|
343
|
+
children: [/* @__PURE__ */ I(l, {
|
|
292
344
|
label: "Contact name",
|
|
293
|
-
children: /* @__PURE__ */
|
|
345
|
+
children: /* @__PURE__ */ I(y, {
|
|
294
346
|
value: u.contactName,
|
|
295
347
|
onChange: g("contactName")
|
|
296
348
|
})
|
|
297
|
-
}), /* @__PURE__ */
|
|
349
|
+
}), /* @__PURE__ */ I(l, {
|
|
298
350
|
label: "Contact email",
|
|
299
|
-
children: /* @__PURE__ */
|
|
351
|
+
children: /* @__PURE__ */ I(y, {
|
|
300
352
|
type: "email",
|
|
301
353
|
value: u.contactEmail,
|
|
302
354
|
onChange: g("contactEmail")
|
|
303
355
|
})
|
|
304
356
|
})]
|
|
305
357
|
}),
|
|
306
|
-
/* @__PURE__ */
|
|
358
|
+
/* @__PURE__ */ I(l, {
|
|
307
359
|
label: "Contact phone",
|
|
308
|
-
children: /* @__PURE__ */
|
|
360
|
+
children: /* @__PURE__ */ I(y, {
|
|
309
361
|
value: u.contactPhone,
|
|
310
362
|
onChange: g("contactPhone")
|
|
311
363
|
})
|
|
312
364
|
}),
|
|
313
|
-
/* @__PURE__ */
|
|
365
|
+
/* @__PURE__ */ I(l, {
|
|
314
366
|
label: "Notes",
|
|
315
|
-
children: /* @__PURE__ */
|
|
367
|
+
children: /* @__PURE__ */ I(v, {
|
|
316
368
|
value: u.notes,
|
|
317
369
|
onChange: g("notes")
|
|
318
370
|
})
|
|
@@ -322,6 +374,6 @@ var R = ["ORGANIZATION", "INDIVIDUAL"], z = [
|
|
|
322
374
|
});
|
|
323
375
|
};
|
|
324
376
|
//#endregion
|
|
325
|
-
export {
|
|
377
|
+
export { H as Avatar, G as ClientsPage };
|
|
326
378
|
|
|
327
379
|
//# sourceMappingURL=ClientsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/ClientsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus, Search, Users, Pencil, Archive, Building2, User, ChevronRight } from 'lucide-react';\nimport {\n useClients,\n useCreateClient,\n useUpdateClient,\n useArchiveClient,\n type PswClient,\n type PswClientStatus,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport { cn } from '../utils/cn';\nimport { useAnalytics } from '../hooks/useAnalytics';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n NativeSelect,\n Banner,\n TableWrap,\n Th,\n Td,\n enumOptions,\n fmtDate,\n} from '../components/ui';\n\nconst CLIENT_TYPES = ['ORGANIZATION', 'INDIVIDUAL'] as const;\nconst CLIENT_STATUSES: PswClientStatus[] = ['PROSPECT', 'ACTIVE', 'INACTIVE', 'ARCHIVED'];\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\n/** Tinted initials avatar (semantic tokens only). */\nexport const Avatar: FC<{ name: string; className?: string }> = ({ name, className }) => (\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent ring-1 ring-inset ring-accent/15',\n className\n )}\n aria-hidden\n >\n {initials(name)}\n </span>\n);\n\ninterface FormState {\n name: string;\n type: string;\n status: string;\n industry: string;\n contactName: string;\n contactEmail: string;\n contactPhone: string;\n notes: string;\n}\nconst emptyForm: FormState = {\n name: '',\n type: 'ORGANIZATION',\n status: 'PROSPECT',\n industry: '',\n contactName: '',\n contactEmail: '',\n contactPhone: '',\n notes: '',\n};\n\nexport const ClientsPage: FC = () => {\n const nav = useNav();\n const { emitIntent, emitFact } = useAnalytics();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<PswClientStatus | ''>('');\n const [editing, setEditing] = useState<PswClient | null>(null);\n const [creating, setCreating] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n\n const { data, isLoading, error, refetch } = useClients({\n search: search.trim() || undefined,\n status: statusFilter || undefined,\n });\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Clients\"\n subtitle=\"Every person or organization your firm serves — the record engagements, matters, documents and invoices hang off.\"\n icon={<Users className=\"h-6 w-6\" />}\n actions={\n <Button\n onClick={() => {\n emitIntent('psw.client.create_clicked');\n setCreating(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> New client\n </Button>\n }\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\n </Banner>\n )}\n\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary\" />\n <TextInput\n placeholder=\"Search clients…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && search.trim()) {\n emitFact('psw.search.performed', {\n entityType: 'client',\n queryLength: search.trim().length,\n });\n }\n }}\n className=\"pl-9\"\n />\n </div>\n <NativeSelect\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as PswClientStatus | '')}\n className=\"sm:w-48\"\n >\n <option value=\"\">All statuses</option>\n {enumOptions(CLIENT_STATUSES)}\n </NativeSelect>\n </div>\n\n {isLoading ? (\n <LoadingBlock />\n ) : error ? (\n <ErrorState error={error} onRetry={() => void refetch()} />\n ) : !data || data.items.length === 0 ? (\n <Empty\n title=\"No clients yet\"\n description=\"Add your first client to start scoping engagements and matters.\"\n icon={<Users className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New client\n </Button>\n }\n />\n ) : (\n <SectionCard title={`${data.totalCount} client${data.totalCount === 1 ? '' : 's'}`}>\n <TableWrap>\n <thead>\n <tr>\n <Th>Name</Th>\n <Th>Type</Th>\n <Th>Industry</Th>\n <Th>Status</Th>\n <Th>Added</Th>\n <Th className=\"text-right\">Actions</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((c) => (\n <tr\n key={c.id}\n className=\"group cursor-pointer transition-colors hover:bg-bg-sunken/60\"\n onClick={() => nav(`clients/${c.id}`)}\n >\n <Td>\n <div className=\"flex items-center gap-3\">\n <Avatar name={c.name} />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1 font-medium text-text-primary transition-colors group-hover:text-accent\">\n <span className=\"truncate\">{c.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 {c.contactName ? (\n <div className=\"truncate text-xs text-text-tertiary\">{c.contactName}</div>\n ) : null}\n </div>\n </div>\n </Td>\n <Td>\n <span className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n {c.type === 'ORGANIZATION' ? (\n <Building2 className=\"h-3.5 w-3.5 text-text-tertiary\" />\n ) : (\n <User className=\"h-3.5 w-3.5 text-text-tertiary\" />\n )}\n {c.type === 'ORGANIZATION' ? 'Organization' : 'Individual'}\n </span>\n </Td>\n <Td>{c.industry || '—'}</Td>\n <Td>\n <StatusPill status={c.status} />\n </Td>\n <Td className=\"whitespace-nowrap\">{fmtDate(c.createdAt)}</Td>\n <Td className=\"text-right\">\n <div\n className=\"flex justify-end gap-1\"\n onClick={(ev) => ev.stopPropagation()}\n role=\"presentation\"\n >\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setEditing(c)}\n aria-label=\"Edit\"\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n {c.status !== 'ARCHIVED' && (\n <ArchiveButton\n id={c.id}\n onDone={(ok) =>\n setBanner(\n ok\n ? { tone: 'success', text: `${c.name} archived.` }\n : { tone: 'danger', text: 'Could not archive client.' }\n )\n }\n />\n )}\n </div>\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </SectionCard>\n )}\n\n {creating && (\n <ClientModal\n onClose={() => setCreating(false)}\n onSaved={(name) => setBanner({ tone: 'success', text: `${name} created.` })}\n />\n )}\n {editing && (\n <ClientModal\n client={editing}\n onClose={() => setEditing(null)}\n onSaved={(name) => setBanner({ tone: 'success', text: `${name} updated.` })}\n />\n )}\n </div>\n );\n};\n\nconst ArchiveButton: FC<{ id: string; onDone: (ok: boolean) => void }> = ({ id, onDone }) => {\n const archive = useArchiveClient();\n const { emitFact } = useAnalytics();\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Archive\"\n disabled={archive.isPending}\n onClick={() => {\n if (!window.confirm('Archive this client? Their engagements and matters remain.')) return;\n archive.mutate(id, {\n onSuccess: (r) => {\n emitFact('psw.client.archived', { clientId: id });\n onDone(Boolean(r));\n },\n onError: () => onDone(false),\n });\n }}\n >\n <Archive className=\"h-4 w-4\" />\n </Button>\n );\n};\n\nconst ClientModal: FC<{\n client?: PswClient;\n onClose: () => void;\n onSaved: (name: string) => void;\n}> = ({ client, onClose, onSaved }) => {\n const create = useCreateClient();\n const update = useUpdateClient();\n const { emitFact } = useAnalytics();\n const [form, setForm] = useState<FormState>(\n client\n ? {\n name: client.name,\n type: client.type,\n status: client.status,\n industry: client.industry ?? '',\n contactName: client.contactName ?? '',\n contactEmail: client.contactEmail ?? '',\n contactPhone: client.contactPhone ?? '',\n notes: client.notes ?? '',\n }\n : emptyForm\n );\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof FormState) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n const pending = create.isPending || update.isPending;\n\n const payload = useMemo(\n () => ({\n name: form.name.trim(),\n type: form.type,\n status: form.status,\n industry: form.industry.trim() || null,\n contactName: form.contactName.trim() || null,\n contactEmail: form.contactEmail.trim() || null,\n contactPhone: form.contactPhone.trim() || null,\n notes: form.notes.trim() || null,\n }),\n [form]\n );\n\n const submit = () => {\n setErr(null);\n if (!payload.name) return setErr('Client name is required.');\n const onError = (e: unknown) => setErr(e instanceof Error ? e.message : 'Save failed.');\n if (client) {\n update.mutate(\n { id: client.id, input: payload },\n {\n onSuccess: () => {\n emitFact('psw.client.updated', { clientId: client.id });\n onSaved(payload.name);\n onClose();\n },\n onError,\n }\n );\n } else {\n create.mutate(payload, {\n onSuccess: (created) => {\n emitFact('psw.client.created', { clientId: created.pswCreateClient.id });\n onSaved(payload.name);\n onClose();\n },\n onError,\n });\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title={client ? 'Edit client' : 'New client'}\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={pending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={pending}>\n {pending ? 'Saving…' : client ? 'Save changes' : 'Create client'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput value={form.name} onChange={set('name')} placeholder=\"Acme Holdings Ltd.\" />\n </Field>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Type\">\n <NativeSelect value={form.type} onChange={set('type')}>\n {enumOptions(CLIENT_TYPES)}\n </NativeSelect>\n </Field>\n <Field label=\"Status\">\n <NativeSelect value={form.status} onChange={set('status')}>\n {enumOptions(CLIENT_STATUSES)}\n </NativeSelect>\n </Field>\n </div>\n <Field label=\"Industry\">\n <TextInput\n value={form.industry}\n onChange={set('industry')}\n placeholder=\"Financial services\"\n />\n </Field>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <Field label=\"Contact name\">\n <TextInput value={form.contactName} onChange={set('contactName')} />\n </Field>\n <Field label=\"Contact email\">\n <TextInput type=\"email\" value={form.contactEmail} onChange={set('contactEmail')} />\n </Field>\n </div>\n <Field label=\"Contact phone\">\n <TextInput value={form.contactPhone} onChange={set('contactPhone')} />\n </Field>\n <Field label=\"Notes\">\n <TextArea value={form.notes} onChange={set('notes')} />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ClientsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/ClientsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus, Search, Users, Pencil, Archive, Building2, User, ChevronRight } from 'lucide-react';\nimport {\n useClients,\n useCreateClient,\n useUpdateClient,\n useArchiveClient,\n type PswClient,\n type PswClientStatus,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport { cn } from '../utils/cn';\nimport { useAnalytics } from '../hooks/useAnalytics';\nimport { t } from '../i18n';\nimport { impactFeedback } from '../utils/native';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n NativeSelect,\n Banner,\n TableWrap,\n Th,\n Td,\n enumOptions,\n fmtDate,\n} from '../components/ui';\nimport { TagPicker } from '../components/TagPicker';\nimport { LoadMore } from '../components/LoadMore';\n\nconst CLIENT_TYPES = ['ORGANIZATION', 'INDIVIDUAL'] as const;\nconst CLIENT_STATUSES: PswClientStatus[] = ['PROSPECT', 'ACTIVE', 'INACTIVE', 'ARCHIVED'];\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\n/** Tinted initials avatar (semantic tokens only). */\nexport const Avatar: FC<{ name: string; className?: string }> = ({ name, className }) => (\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent ring-1 ring-inset ring-accent/15',\n className\n )}\n aria-hidden\n >\n {initials(name)}\n </span>\n);\n\ninterface FormState {\n name: string;\n type: string;\n status: string;\n industry: string;\n contactName: string;\n contactEmail: string;\n contactPhone: string;\n notes: string;\n}\nconst emptyForm: FormState = {\n name: '',\n type: 'ORGANIZATION',\n status: 'PROSPECT',\n industry: '',\n contactName: '',\n contactEmail: '',\n contactPhone: '',\n notes: '',\n};\n\nconst PAGE_SIZE = 25;\n\nexport const ClientsPage: FC = () => {\n const nav = useNav();\n const { emitIntent, emitFact } = useAnalytics();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<PswClientStatus | ''>('');\n const [editing, setEditing] = useState<PswClient | null>(null);\n const [creating, setCreating] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n const [pagination, setPagination] = useState({ limit: PAGE_SIZE, offset: 0 });\n\n const { data, isLoading, error, refetch } = useClients({\n search: search.trim() || undefined,\n status: statusFilter || undefined,\n pagination,\n });\n const update = useUpdateClient();\n\n const allItems = useMemo(() => data?.items ?? [], [data]);\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title={t('psw.clients.title')}\n subtitle={t('psw.clients.subtitle')}\n icon={<Users className=\"h-6 w-6\" />}\n actions={\n <Button\n onClick={() => {\n void impactFeedback('light');\n emitIntent('psw.client.create_clicked');\n setCreating(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> {t('psw.clients.new')}\n </Button>\n }\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\n </Banner>\n )}\n\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary\" />\n <TextInput\n placeholder={t('psw.clients.searchPlaceholder')}\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPagination({ limit: PAGE_SIZE, offset: 0 });\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && search.trim()) {\n emitFact('psw.search.performed', {\n entityType: 'client',\n queryLength: search.trim().length,\n });\n }\n }}\n className=\"pl-9\"\n />\n </div>\n <NativeSelect\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as PswClientStatus | '');\n setPagination({ limit: PAGE_SIZE, offset: 0 });\n }}\n className=\"sm:w-48\"\n >\n <option value=\"\">{t('psw.clients.allStatuses')}</option>\n {enumOptions(CLIENT_STATUSES)}\n </NativeSelect>\n </div>\n\n {isLoading ? (\n <LoadingBlock />\n ) : error ? (\n <ErrorState error={error} onRetry={() => void refetch()} />\n ) : !data || data.items.length === 0 ? (\n <Empty\n title={t('psw.clients.emptyTitle')}\n description={t('psw.clients.emptyDescription')}\n icon={<Users className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> {t('psw.clients.new')}\n </Button>\n }\n />\n ) : (\n <SectionCard title={`${data.totalCount} client${data.totalCount === 1 ? '' : 's'}`}>\n <TableWrap>\n <thead>\n <tr>\n <Th>{t('psw.common.name')}</Th>\n <Th>{t('psw.common.type')}</Th>\n <Th>Industry</Th>\n <Th>{t('psw.common.status')}</Th>\n <Th>{t('psw.common.added')}</Th>\n <Th>Tags</Th>\n <Th className=\"text-right\">{t('psw.common.actions')}</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((c) => (\n <tr\n key={c.id}\n className=\"group cursor-pointer transition-colors hover:bg-bg-sunken/60\"\n onClick={() => nav(`clients/${c.id}`)}\n >\n <Td>\n <div className=\"flex items-center gap-3\">\n <Avatar name={c.name} />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1 font-medium text-text-primary transition-colors group-hover:text-accent\">\n <span className=\"truncate\">{c.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 {c.contactName ? (\n <div className=\"truncate text-xs text-text-tertiary\">{c.contactName}</div>\n ) : null}\n </div>\n </div>\n </Td>\n <Td>\n <span className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n {c.type === 'ORGANIZATION' ? (\n <Building2 className=\"h-3.5 w-3.5 text-text-tertiary\" />\n ) : (\n <User className=\"h-3.5 w-3.5 text-text-tertiary\" />\n )}\n {c.type === 'ORGANIZATION' ? 'Organization' : 'Individual'}\n </span>\n </Td>\n <Td>{c.industry || '—'}</Td>\n <Td>\n <StatusPill status={c.status} />\n </Td>\n <Td className=\"whitespace-nowrap\">{fmtDate(c.createdAt)}</Td>\n <Td>\n <div onClick={(ev) => ev.stopPropagation()} role=\"presentation\">\n <TagPicker\n entityType=\"PswClient\"\n entityId={c.id}\n tags={c.tags ?? []}\n onChange={async (tags) => {\n await update.mutateAsync({ id: c.id, input: { tags } });\n void refetch();\n }}\n maxTags={5}\n />\n </div>\n </Td>\n <Td className=\"text-right\">\n <div\n className=\"flex justify-end gap-1\"\n onClick={(ev) => ev.stopPropagation()}\n role=\"presentation\"\n >\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setEditing(c)}\n aria-label=\"Edit\"\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n {c.status !== 'ARCHIVED' && (\n <ArchiveButton\n id={c.id}\n onDone={(ok) =>\n setBanner(\n ok\n ? { tone: 'success', text: `${c.name} archived.` }\n : { tone: 'danger', text: 'Could not archive client.' }\n )\n }\n />\n )}\n </div>\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n <LoadMore\n hasMore={data?.pageInfo?.hasMore ?? false}\n loading={isLoading}\n loadedCount={allItems.length}\n totalCount={data?.totalCount ?? allItems.length}\n onLoadMore={() =>\n setPagination((p) => ({ limit: PAGE_SIZE, offset: p.offset + PAGE_SIZE }))\n }\n />\n </SectionCard>\n )}\n\n {creating && (\n <ClientModal\n onClose={() => setCreating(false)}\n onSaved={(name) => setBanner({ tone: 'success', text: `${name} created.` })}\n />\n )}\n {editing && (\n <ClientModal\n client={editing}\n onClose={() => setEditing(null)}\n onSaved={(name) => setBanner({ tone: 'success', text: `${name} updated.` })}\n />\n )}\n </div>\n );\n};\n\nconst ArchiveButton: FC<{ id: string; onDone: (ok: boolean) => void }> = ({ id, onDone }) => {\n const archive = useArchiveClient();\n const { emitFact } = useAnalytics();\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Archive\"\n disabled={archive.isPending}\n onClick={() => {\n if (!window.confirm('Archive this client? Their engagements and matters remain.')) return;\n archive.mutate(id, {\n onSuccess: (r) => {\n emitFact('psw.client.archived', { clientId: id });\n onDone(Boolean(r));\n },\n onError: () => onDone(false),\n });\n }}\n >\n <Archive className=\"h-4 w-4\" />\n </Button>\n );\n};\n\nconst ClientModal: FC<{\n client?: PswClient;\n onClose: () => void;\n onSaved: (name: string) => void;\n}> = ({ client, onClose, onSaved }) => {\n const create = useCreateClient();\n const update = useUpdateClient();\n const { emitFact } = useAnalytics();\n const [form, setForm] = useState<FormState>(\n client\n ? {\n name: client.name,\n type: client.type,\n status: client.status,\n industry: client.industry ?? '',\n contactName: client.contactName ?? '',\n contactEmail: client.contactEmail ?? '',\n contactPhone: client.contactPhone ?? '',\n notes: client.notes ?? '',\n }\n : emptyForm\n );\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof FormState) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n const pending = create.isPending || update.isPending;\n\n const payload = useMemo(\n () => ({\n name: form.name.trim(),\n type: form.type,\n status: form.status,\n industry: form.industry.trim() || null,\n contactName: form.contactName.trim() || null,\n contactEmail: form.contactEmail.trim() || null,\n contactPhone: form.contactPhone.trim() || null,\n notes: form.notes.trim() || null,\n }),\n [form]\n );\n\n const submit = () => {\n setErr(null);\n if (!payload.name) return setErr('Client name is required.');\n const onError = (e: unknown) => setErr(e instanceof Error ? e.message : 'Save failed.');\n if (client) {\n update.mutate(\n { id: client.id, input: payload },\n {\n onSuccess: () => {\n emitFact('psw.client.updated', { clientId: client.id });\n onSaved(payload.name);\n onClose();\n },\n onError,\n }\n );\n } else {\n create.mutate(payload, {\n onSuccess: (created) => {\n emitFact('psw.client.created', { clientId: created.pswCreateClient.id });\n onSaved(payload.name);\n onClose();\n },\n onError,\n });\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title={client ? 'Edit client' : 'New client'}\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={pending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={pending}>\n {pending ? 'Saving…' : client ? 'Save changes' : 'Create client'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput value={form.name} onChange={set('name')} placeholder=\"Acme Holdings Ltd.\" />\n </Field>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Type\">\n <NativeSelect value={form.type} onChange={set('type')}>\n {enumOptions(CLIENT_TYPES)}\n </NativeSelect>\n </Field>\n <Field label=\"Status\">\n <NativeSelect value={form.status} onChange={set('status')}>\n {enumOptions(CLIENT_STATUSES)}\n </NativeSelect>\n </Field>\n </div>\n <Field label=\"Industry\">\n <TextInput\n value={form.industry}\n onChange={set('industry')}\n placeholder=\"Financial services\"\n />\n </Field>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <Field label=\"Contact name\">\n <TextInput value={form.contactName} onChange={set('contactName')} />\n </Field>\n <Field label=\"Contact email\">\n <TextInput type=\"email\" value={form.contactEmail} onChange={set('contactEmail')} />\n </Field>\n </div>\n <Field label=\"Contact phone\">\n <TextInput value={form.contactPhone} onChange={set('contactPhone')} />\n </Field>\n <Field label=\"Notes\">\n <TextArea value={form.notes} onChange={set('notes')} />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
|