@burdenoff/microfe-proserviceworld 2026.703.1 → 2026.706.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 (75) hide show
  1. package/dist/index.js +2 -3
  2. package/dist/node_modules/clsx/dist/clsx.js +18 -0
  3. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  4. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
  5. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  6. package/dist/proserviceworld/ProServiceWorldRoot.js +92 -136
  7. package/dist/proserviceworld/ProServiceWorldRoot.js.map +1 -1
  8. package/dist/proserviceworld/ProServiceWorldRoutes.js +18 -26
  9. package/dist/proserviceworld/ProServiceWorldRoutes.js.map +1 -1
  10. package/dist/proserviceworld/components/ui.js +273 -0
  11. package/dist/proserviceworld/components/ui.js.map +1 -0
  12. package/dist/proserviceworld/hooks/useFiles.js +45 -0
  13. package/dist/proserviceworld/hooks/useFiles.js.map +1 -0
  14. package/dist/proserviceworld/hooks/useProServiceWorldApi.js +416 -0
  15. package/dist/proserviceworld/hooks/useProServiceWorldApi.js.map +1 -0
  16. package/dist/proserviceworld/index.js +0 -1
  17. package/dist/proserviceworld/pages/AnalyticsPage.js +163 -475
  18. package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
  19. package/dist/proserviceworld/pages/ClientDetailPage.js +238 -0
  20. package/dist/proserviceworld/pages/ClientDetailPage.js.map +1 -0
  21. package/dist/proserviceworld/pages/ClientsPage.js +295 -0
  22. package/dist/proserviceworld/pages/ClientsPage.js.map +1 -0
  23. package/dist/proserviceworld/pages/CompliancePage.js +307 -0
  24. package/dist/proserviceworld/pages/CompliancePage.js.map +1 -0
  25. package/dist/proserviceworld/pages/ConflictsPage.js +234 -0
  26. package/dist/proserviceworld/pages/ConflictsPage.js.map +1 -0
  27. package/dist/proserviceworld/pages/DashboardPage.js +179 -632
  28. package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
  29. package/dist/proserviceworld/pages/DocumentsPage.js +235 -539
  30. package/dist/proserviceworld/pages/DocumentsPage.js.map +1 -1
  31. package/dist/proserviceworld/pages/EngagementDetailPage.js +444 -0
  32. package/dist/proserviceworld/pages/EngagementDetailPage.js.map +1 -0
  33. package/dist/proserviceworld/pages/EngagementsPage.js +256 -0
  34. package/dist/proserviceworld/pages/EngagementsPage.js.map +1 -0
  35. package/dist/proserviceworld/pages/InvoicesPage.js +303 -0
  36. package/dist/proserviceworld/pages/InvoicesPage.js.map +1 -0
  37. package/dist/proserviceworld/pages/MattersPage.js +275 -0
  38. package/dist/proserviceworld/pages/MattersPage.js.map +1 -0
  39. package/dist/proserviceworld/pages/PracticeAreasPage.js +176 -0
  40. package/dist/proserviceworld/pages/PracticeAreasPage.js.map +1 -0
  41. package/dist/proserviceworld/pages/TimePage.js +310 -0
  42. package/dist/proserviceworld/pages/TimePage.js.map +1 -0
  43. package/dist/proserviceworld/utils/cn.js +10 -0
  44. package/dist/proserviceworld/utils/cn.js.map +1 -0
  45. package/dist/proserviceworld/utils/nav.js +11 -0
  46. package/dist/proserviceworld/utils/nav.js.map +1 -0
  47. package/package.json +1 -1
  48. package/dist/proserviceworld/pages/AdminReportsPage.js +0 -579
  49. package/dist/proserviceworld/pages/AdminReportsPage.js.map +0 -1
  50. package/dist/proserviceworld/pages/AdminUsersPage.js +0 -468
  51. package/dist/proserviceworld/pages/AdminUsersPage.js.map +0 -1
  52. package/dist/proserviceworld/pages/BookingsPage.js +0 -446
  53. package/dist/proserviceworld/pages/BookingsPage.js.map +0 -1
  54. package/dist/proserviceworld/pages/MessagesPage.js +0 -238
  55. package/dist/proserviceworld/pages/MessagesPage.js.map +0 -1
  56. package/dist/proserviceworld/pages/PaymentsPage.js +0 -537
  57. package/dist/proserviceworld/pages/PaymentsPage.js.map +0 -1
  58. package/dist/proserviceworld/pages/ProjectsPage.js +0 -627
  59. package/dist/proserviceworld/pages/ProjectsPage.js.map +0 -1
  60. package/dist/proserviceworld/pages/ProviderDetailPage.js +0 -538
  61. package/dist/proserviceworld/pages/ProviderDetailPage.js.map +0 -1
  62. package/dist/proserviceworld/pages/ProvidersPage.js +0 -347
  63. package/dist/proserviceworld/pages/ProvidersPage.js.map +0 -1
  64. package/dist/proserviceworld/pages/ReviewsPage.js +0 -356
  65. package/dist/proserviceworld/pages/ReviewsPage.js.map +0 -1
  66. package/dist/proserviceworld/pages/ServiceDetailsPage.js +0 -631
  67. package/dist/proserviceworld/pages/ServiceDetailsPage.js.map +0 -1
  68. package/dist/proserviceworld/pages/ServicesPage.js +0 -344
  69. package/dist/proserviceworld/pages/ServicesPage.js.map +0 -1
  70. package/dist/proserviceworld/pages/VerificationPage.js +0 -370
  71. package/dist/proserviceworld/pages/VerificationPage.js.map +0 -1
  72. package/dist/proserviceworld/store/proserviceworldStore.js +0 -2490
  73. package/dist/proserviceworld/store/proserviceworldStore.js.map +0 -1
  74. package/dist/proserviceworld/utils/i18n.js +0 -15
  75. package/dist/proserviceworld/utils/i18n.js.map +0 -1
@@ -0,0 +1,256 @@
1
+ import { useClients as e, useCreateEngagement as t, useEngagements as n, usePracticeAreas as r } from "../hooks/useProServiceWorldApi.js";
2
+ import { useNav as i } from "../utils/nav.js";
3
+ import { Banner as a, Empty as o, ErrorState as s, Field as c, LoadingBlock as l, Modal as u, NativeSelect as d, PageHeader as f, SectionCard as p, StatusPill as m, TableWrap as h, Td as g, TextArea as _, TextInput as v, Th as y, enumOptions as b, fmtDate as x, fmtMoney as S } from "../components/ui.js";
4
+ import { useState as C } from "react";
5
+ import { Briefcase as w, Plus as T, Search as E } from "lucide-react";
6
+ import { Fragment as D, jsx as O, jsxs as k } from "react/jsx-runtime";
7
+ import { Button as A } from "@burdenoff/fe-libs/ui";
8
+ //#region src/proserviceworld/pages/EngagementsPage.tsx
9
+ var j = [
10
+ "PROPOSAL",
11
+ "ACCEPTED",
12
+ "IN_PROGRESS",
13
+ "REVIEW",
14
+ "DELIVERED",
15
+ "INVOICED",
16
+ "CLOSED",
17
+ "CANCELLED"
18
+ ], M = [
19
+ "FIXED",
20
+ "HOURLY",
21
+ "RETAINER",
22
+ "VALUE_BASED"
23
+ ], N = () => {
24
+ let e = i(), [t, r] = C(""), [c, u] = C(""), [_, D] = C(!1), [M, N] = C(null), { data: F, isLoading: I, error: L, refetch: R } = n({
25
+ search: t.trim() || void 0,
26
+ status: c || void 0
27
+ });
28
+ return /* @__PURE__ */ k("div", {
29
+ className: "space-y-6",
30
+ children: [
31
+ /* @__PURE__ */ O(f, {
32
+ title: "Engagements",
33
+ subtitle: "The commercial containers of contracted work — scope, fee arrangement, delivery lifecycle, and billing.",
34
+ icon: /* @__PURE__ */ O(w, { className: "h-6 w-6" }),
35
+ actions: /* @__PURE__ */ k(A, {
36
+ onClick: () => D(!0),
37
+ children: [/* @__PURE__ */ O(T, { className: "mr-1.5 h-4 w-4" }), "New engagement"]
38
+ })
39
+ }),
40
+ M && /* @__PURE__ */ O(a, {
41
+ tone: "success",
42
+ onClose: () => N(null),
43
+ children: M
44
+ }),
45
+ /* @__PURE__ */ k("div", {
46
+ className: "flex flex-col gap-2 sm:flex-row sm:items-center",
47
+ children: [/* @__PURE__ */ k("div", {
48
+ className: "relative flex-1",
49
+ children: [/* @__PURE__ */ O(E, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary" }), /* @__PURE__ */ O(v, {
50
+ placeholder: "Search engagements…",
51
+ value: t,
52
+ onChange: (e) => r(e.target.value),
53
+ className: "pl-9"
54
+ })]
55
+ }), /* @__PURE__ */ k(d, {
56
+ value: c,
57
+ onChange: (e) => u(e.target.value),
58
+ className: "sm:w-52",
59
+ children: [/* @__PURE__ */ O("option", {
60
+ value: "",
61
+ children: "All statuses"
62
+ }), b(j)]
63
+ })]
64
+ }),
65
+ I ? /* @__PURE__ */ O(l, {}) : L ? /* @__PURE__ */ O(s, {
66
+ error: L,
67
+ onRetry: () => void R()
68
+ }) : !F || F.items.length === 0 ? /* @__PURE__ */ O(o, {
69
+ title: "No engagements yet",
70
+ description: "Scope your first engagement against a client to begin delivery and billing.",
71
+ icon: /* @__PURE__ */ O(w, { className: "h-8 w-8" }),
72
+ action: /* @__PURE__ */ k(A, {
73
+ onClick: () => D(!0),
74
+ children: [/* @__PURE__ */ O(T, { className: "mr-1.5 h-4 w-4" }), "New engagement"]
75
+ })
76
+ }) : /* @__PURE__ */ O(p, {
77
+ title: `${F.totalCount} engagement${F.totalCount === 1 ? "" : "s"}`,
78
+ children: /* @__PURE__ */ k(h, { children: [/* @__PURE__ */ O("thead", { children: /* @__PURE__ */ k("tr", { children: [
79
+ /* @__PURE__ */ O(y, { children: "Engagement" }),
80
+ /* @__PURE__ */ O(y, { children: "Client" }),
81
+ /* @__PURE__ */ O(y, { children: "Fee" }),
82
+ /* @__PURE__ */ O(y, { children: "Status" }),
83
+ /* @__PURE__ */ O(y, { children: "Started" })
84
+ ] }) }), /* @__PURE__ */ O("tbody", { children: F.items.map((t) => /* @__PURE__ */ k("tr", {
85
+ className: "cursor-pointer hover:bg-bg-sunken/50",
86
+ onClick: () => e(`engagements/${t.id}`),
87
+ children: [
88
+ /* @__PURE__ */ O(g, { children: /* @__PURE__ */ O("span", {
89
+ className: "font-medium text-text-primary",
90
+ children: t.name
91
+ }) }),
92
+ /* @__PURE__ */ O(g, { children: t.client?.name ?? "—" }),
93
+ /* @__PURE__ */ k(g, { children: [
94
+ S(t.feeAmount, t.currency),
95
+ " ",
96
+ /* @__PURE__ */ k("span", {
97
+ className: "text-xs text-text-tertiary",
98
+ children: ["/ ", t.feeArrangement.toLowerCase()]
99
+ })
100
+ ] }),
101
+ /* @__PURE__ */ O(g, { children: /* @__PURE__ */ O(m, { status: t.status }) }),
102
+ /* @__PURE__ */ O(g, { children: x(t.startDate) })
103
+ ]
104
+ }, t.id)) })] })
105
+ }),
106
+ _ && /* @__PURE__ */ O(P, {
107
+ onClose: () => D(!1),
108
+ onSaved: (e) => N(`${e} created.`)
109
+ })
110
+ ]
111
+ });
112
+ }, P = ({ onClose: n, onSaved: i }) => {
113
+ let o = t(), s = e({ pagination: { limit: 100 } }), l = r(), [f, p] = C({
114
+ clientId: "",
115
+ practiceAreaId: "",
116
+ name: "",
117
+ description: "",
118
+ feeArrangement: "FIXED",
119
+ feeAmount: "",
120
+ currency: "USD",
121
+ startDate: ""
122
+ }), [m, h] = C(null), g = (e) => (t) => p((n) => ({
123
+ ...n,
124
+ [e]: t.target.value
125
+ }));
126
+ return /* @__PURE__ */ O(u, {
127
+ open: !0,
128
+ onClose: n,
129
+ title: "New engagement",
130
+ footer: /* @__PURE__ */ k(D, { children: [/* @__PURE__ */ O(A, {
131
+ variant: "outline",
132
+ onClick: n,
133
+ disabled: o.isPending,
134
+ children: "Cancel"
135
+ }), /* @__PURE__ */ O(A, {
136
+ onClick: () => {
137
+ if (h(null), !f.clientId) return h("Select a client.");
138
+ if (!f.name.trim()) return h("Engagement name is required.");
139
+ o.mutate({
140
+ clientId: f.clientId,
141
+ practiceAreaId: f.practiceAreaId || null,
142
+ name: f.name.trim(),
143
+ description: f.description.trim() || null,
144
+ feeArrangement: f.feeArrangement,
145
+ feeAmount: f.feeAmount ? Number(f.feeAmount) : 0,
146
+ currency: f.currency || "USD",
147
+ startDate: f.startDate ? new Date(f.startDate).toISOString() : null
148
+ }, {
149
+ onSuccess: () => {
150
+ i(f.name.trim()), n();
151
+ },
152
+ onError: (e) => h(e instanceof Error ? e.message : "Failed.")
153
+ });
154
+ },
155
+ disabled: o.isPending,
156
+ children: o.isPending ? "Creating…" : "Create engagement"
157
+ })] }),
158
+ children: /* @__PURE__ */ k("div", {
159
+ className: "space-y-3",
160
+ children: [
161
+ m && /* @__PURE__ */ O(a, {
162
+ tone: "danger",
163
+ children: m
164
+ }),
165
+ /* @__PURE__ */ O(c, {
166
+ label: "Client",
167
+ required: !0,
168
+ children: /* @__PURE__ */ k(d, {
169
+ value: f.clientId,
170
+ onChange: g("clientId"),
171
+ children: [/* @__PURE__ */ O("option", {
172
+ value: "",
173
+ children: "Select a client…"
174
+ }), s.data?.items.map((e) => /* @__PURE__ */ O("option", {
175
+ value: e.id,
176
+ children: e.name
177
+ }, e.id))]
178
+ })
179
+ }),
180
+ /* @__PURE__ */ O(c, {
181
+ label: "Name",
182
+ required: !0,
183
+ children: /* @__PURE__ */ O(v, {
184
+ value: f.name,
185
+ onChange: g("name"),
186
+ placeholder: "FY26 Statutory Audit"
187
+ })
188
+ }),
189
+ /* @__PURE__ */ O(c, {
190
+ label: "Practice area",
191
+ children: /* @__PURE__ */ k(d, {
192
+ value: f.practiceAreaId,
193
+ onChange: g("practiceAreaId"),
194
+ children: [/* @__PURE__ */ O("option", {
195
+ value: "",
196
+ children: "Unassigned"
197
+ }), l.data?.map((e) => /* @__PURE__ */ O("option", {
198
+ value: e.id,
199
+ children: e.name
200
+ }, e.id))]
201
+ })
202
+ }),
203
+ /* @__PURE__ */ k("div", {
204
+ className: "grid grid-cols-3 gap-3",
205
+ children: [
206
+ /* @__PURE__ */ O(c, {
207
+ label: "Fee type",
208
+ children: /* @__PURE__ */ O(d, {
209
+ value: f.feeArrangement,
210
+ onChange: g("feeArrangement"),
211
+ children: b(M)
212
+ })
213
+ }),
214
+ /* @__PURE__ */ O(c, {
215
+ label: "Fee amount",
216
+ children: /* @__PURE__ */ O(v, {
217
+ type: "number",
218
+ min: "0",
219
+ value: f.feeAmount,
220
+ onChange: g("feeAmount"),
221
+ placeholder: "50000"
222
+ })
223
+ }),
224
+ /* @__PURE__ */ O(c, {
225
+ label: "Currency",
226
+ children: /* @__PURE__ */ O(v, {
227
+ value: f.currency,
228
+ onChange: g("currency"),
229
+ maxLength: 3
230
+ })
231
+ })
232
+ ]
233
+ }),
234
+ /* @__PURE__ */ O(c, {
235
+ label: "Start date",
236
+ children: /* @__PURE__ */ O(v, {
237
+ type: "date",
238
+ value: f.startDate,
239
+ onChange: g("startDate")
240
+ })
241
+ }),
242
+ /* @__PURE__ */ O(c, {
243
+ label: "Description",
244
+ children: /* @__PURE__ */ O(_, {
245
+ value: f.description,
246
+ onChange: g("description")
247
+ })
248
+ })
249
+ ]
250
+ })
251
+ });
252
+ };
253
+ //#endregion
254
+ export { N as EngagementsPage };
255
+
256
+ //# sourceMappingURL=EngagementsPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EngagementsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/EngagementsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus, Search, Briefcase } from 'lucide-react';\nimport {\n useEngagements,\n useCreateEngagement,\n useClients,\n usePracticeAreas,\n type PswEngagementStatus,\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 NativeSelect,\n Banner,\n TableWrap,\n Th,\n Td,\n enumOptions,\n fmtMoney,\n fmtDate,\n} from '../components/ui';\n\nconst STATUSES: PswEngagementStatus[] = [\n 'PROPOSAL',\n 'ACCEPTED',\n 'IN_PROGRESS',\n 'REVIEW',\n 'DELIVERED',\n 'INVOICED',\n 'CLOSED',\n 'CANCELLED',\n];\nconst FEE_ARRANGEMENTS = ['FIXED', 'HOURLY', 'RETAINER', 'VALUE_BASED'] as const;\n\nexport const EngagementsPage: FC = () => {\n const nav = useNav();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<PswEngagementStatus | ''>('');\n const [creating, setCreating] = useState(false);\n const [banner, setBanner] = useState<string | null>(null);\n const { data, isLoading, error, refetch } = useEngagements({\n search: search.trim() || undefined,\n status: statusFilter || undefined,\n });\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Engagements\"\n subtitle=\"The commercial containers of contracted work — scope, fee arrangement, delivery lifecycle, and billing.\"\n icon={<Briefcase className=\"h-6 w-6\" />}\n actions={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n New engagement\n </Button>\n }\n />\n\n {banner && (\n <Banner tone=\"success\" onClose={() => setBanner(null)}>\n {banner}\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 engagements…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"pl-9\"\n />\n </div>\n <NativeSelect\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as PswEngagementStatus | '')}\n className=\"sm:w-52\"\n >\n <option value=\"\">All statuses</option>\n {enumOptions(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 engagements yet\"\n description=\"Scope your first engagement against a client to begin delivery and billing.\"\n icon={<Briefcase className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n New engagement\n </Button>\n }\n />\n ) : (\n <SectionCard title={`${data.totalCount} engagement${data.totalCount === 1 ? '' : 's'}`}>\n <TableWrap>\n <thead>\n <tr>\n <Th>Engagement</Th>\n <Th>Client</Th>\n <Th>Fee</Th>\n <Th>Status</Th>\n <Th>Started</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((e) => (\n <tr\n key={e.id}\n className=\"cursor-pointer hover:bg-bg-sunken/50\"\n onClick={() => nav(`engagements/${e.id}`)}\n >\n <Td>\n <span className=\"font-medium text-text-primary\">{e.name}</span>\n </Td>\n <Td>{e.client?.name ?? '—'}</Td>\n <Td>\n {fmtMoney(e.feeAmount, e.currency)}{' '}\n <span className=\"text-xs text-text-tertiary\">\n / {e.feeArrangement.toLowerCase()}\n </span>\n </Td>\n <Td>\n <StatusPill status={e.status} />\n </Td>\n <Td>{fmtDate(e.startDate)}</Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </SectionCard>\n )}\n\n {creating && (\n <EngagementModal\n onClose={() => setCreating(false)}\n onSaved={(name) => setBanner(`${name} created.`)}\n />\n )}\n </div>\n );\n};\n\nconst EngagementModal: FC<{ onClose: () => void; onSaved: (name: string) => void }> = ({\n onClose,\n onSaved,\n}) => {\n const create = useCreateEngagement();\n const clients = useClients({ pagination: { limit: 100 } });\n const practiceAreas = usePracticeAreas();\n const [form, setForm] = useState({\n clientId: '',\n practiceAreaId: '',\n name: '',\n description: '',\n feeArrangement: 'FIXED',\n feeAmount: '',\n currency: 'USD',\n startDate: '',\n });\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof typeof form) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n\n const submit = () => {\n setErr(null);\n if (!form.clientId) return setErr('Select a client.');\n if (!form.name.trim()) return setErr('Engagement name is required.');\n create.mutate(\n {\n clientId: form.clientId,\n practiceAreaId: form.practiceAreaId || null,\n name: form.name.trim(),\n description: form.description.trim() || null,\n feeArrangement: form.feeArrangement,\n feeAmount: form.feeAmount ? Number(form.feeAmount) : 0,\n currency: form.currency || 'USD',\n startDate: form.startDate ? new Date(form.startDate).toISOString() : null,\n },\n {\n onSuccess: () => {\n onSaved(form.name.trim());\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=\"New engagement\"\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 ? 'Creating…' : 'Create engagement'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Client\" required>\n <NativeSelect value={form.clientId} onChange={set('clientId')}>\n <option value=\"\">Select a client…</option>\n {clients.data?.items.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </NativeSelect>\n </Field>\n <Field label=\"Name\" required>\n <TextInput value={form.name} onChange={set('name')} placeholder=\"FY26 Statutory Audit\" />\n </Field>\n <Field label=\"Practice area\">\n <NativeSelect value={form.practiceAreaId} onChange={set('practiceAreaId')}>\n <option value=\"\">Unassigned</option>\n {practiceAreas.data?.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </NativeSelect>\n </Field>\n <div className=\"grid grid-cols-3 gap-3\">\n <Field label=\"Fee type\">\n <NativeSelect value={form.feeArrangement} onChange={set('feeArrangement')}>\n {enumOptions(FEE_ARRANGEMENTS)}\n </NativeSelect>\n </Field>\n <Field label=\"Fee amount\">\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.feeAmount}\n onChange={set('feeAmount')}\n placeholder=\"50000\"\n />\n </Field>\n <Field label=\"Currency\">\n <TextInput value={form.currency} onChange={set('currency')} maxLength={3} />\n </Field>\n </div>\n <Field label=\"Start date\">\n <TextInput type=\"date\" value={form.startDate} onChange={set('startDate')} />\n </Field>\n <Field label=\"Description\">\n <TextArea value={form.description} onChange={set('description')} />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":";;;;;;;;AAiCA,IAAM,IAAkC;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EACK,IAAmB;CAAC;CAAS;CAAU;CAAY;CAAc,EAE1D,UAA4B;CACvC,IAAM,IAAM,GAAQ,EACd,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAc,KAAmB,EAAmC,GAAG,EACxE,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,CAAC,GAAQ,KAAa,EAAwB,KAAK,EACnD,EAAE,SAAM,cAAW,UAAO,eAAY,EAAe;EACzD,QAAQ,EAAO,MAAM,IAAI,KAAA;EACzB,QAAQ,KAAgB,KAAA;EACzB,CAAC;AAEF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,OAAM;IACN,UAAS;IACT,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;IACvC,SACE,kBAAC,GAAD;KAAQ,eAAe,EAAY,GAAK;eAAxC,CACE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,iBAE5B;;IAEX,CAAA;GAED,KACC,kBAAC,GAAD;IAAQ,MAAK;IAAU,eAAe,EAAU,KAAK;cAClD;IACM,CAAA;GAGX,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,2FAA4F,CAAA,EAC9G,kBAAC,GAAD;MACE,aAAY;MACZ,OAAO;MACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;MAC1C,WAAU;MACV,CAAA,CACE;QACN,kBAAC,GAAD;KACE,OAAO;KACP,WAAW,MAAM,EAAgB,EAAE,OAAO,MAAkC;KAC5E,WAAU;eAHZ,CAKE,kBAAC,UAAD;MAAQ,OAAM;gBAAG;MAAqB,CAAA,EACrC,EAAY,EAAS,CACT;OACX;;GAEL,IACC,kBAAC,GAAD,EAAgB,CAAA,GACd,IACF,kBAAC,GAAD;IAAmB;IAAO,eAAe,KAAK,GAAS;IAAI,CAAA,GACzD,CAAC,KAAQ,EAAK,MAAM,WAAW,IACjC,kBAAC,GAAD;IACE,OAAM;IACN,aAAY;IACZ,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;IACvC,QACE,kBAAC,GAAD;KAAQ,eAAe,EAAY,GAAK;eAAxC,CACE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,iBAE5B;;IAEX,CAAA,GAEF,kBAAC,GAAD;IAAa,OAAO,GAAG,EAAK,WAAW,aAAa,EAAK,eAAe,IAAI,KAAK;cAC/E,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD,EAAA,UAAA;KACE,kBAAC,GAAD,EAAA,UAAI,cAAe,CAAA;KACnB,kBAAC,GAAD,EAAA,UAAI,UAAW,CAAA;KACf,kBAAC,GAAD,EAAA,UAAI,OAAQ,CAAA;KACZ,kBAAC,GAAD,EAAA,UAAI,UAAW,CAAA;KACf,kBAAC,GAAD,EAAA,UAAI,WAAY,CAAA;KACb,EAAA,CAAA,EACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAK,MAAM,KAAK,MACf,kBAAC,MAAD;KAEE,WAAU;KACV,eAAe,EAAI,eAAe,EAAE,KAAK;eAH3C;MAKE,kBAAC,GAAD,EAAA,UACE,kBAAC,QAAD;OAAM,WAAU;iBAAiC,EAAE;OAAY,CAAA,EAC5D,CAAA;MACL,kBAAC,GAAD,EAAA,UAAK,EAAE,QAAQ,QAAQ,KAAS,CAAA;MAChC,kBAAC,GAAD,EAAA,UAAA;OACG,EAAS,EAAE,WAAW,EAAE,SAAS;OAAE;OACpC,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAA6C,MACxC,EAAE,eAAe,aAAa,CAC5B;;OACJ,EAAA,CAAA;MACL,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAY,QAAQ,EAAE,QAAU,CAAA,EAC7B,CAAA;MACL,kBAAC,GAAD,EAAA,UAAK,EAAQ,EAAE,UAAU,EAAM,CAAA;MAC5B;OAlBE,EAAE,GAkBJ,CACL,EACI,CAAA,CACE,EAAA,CAAA;IACA,CAAA;GAGf,KACC,kBAAC,GAAD;IACE,eAAe,EAAY,GAAM;IACjC,UAAU,MAAS,EAAU,GAAG,EAAK,WAAW;IAChD,CAAA;GAEA;;GAIJ,KAAiF,EACrF,YACA,iBACI;CACJ,IAAM,IAAS,GAAqB,EAC9B,IAAU,EAAW,EAAE,YAAY,EAAE,OAAO,KAAK,EAAE,CAAC,EACpD,IAAgB,GAAkB,EAClC,CAAC,GAAM,KAAW,EAAS;EAC/B,UAAU;EACV,gBAAgB;EAChB,MAAM;EACN,aAAa;EACb,gBAAgB;EAChB,WAAW;EACX,UAAU;EACV,WAAW;EACZ,CAAC,EACI,CAAC,GAAK,KAAU,EAAwB,KAAK,EAC7C,KAAO,OAA0B,MACrC,GAAS,OAAO;EAAE,GAAG;GAAI,IAAI,EAAE,OAAO;EAAO,EAAE;AA2BjD,QACE,kBAAC,GAAD;EACE,MAAA;EACS;EACT,OAAM;EACN,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,SAAS;GAAS,UAAU,EAAO;aAAW;GAE/D,CAAA,EACT,kBAAC,GAAD;GAAQ,eAnCK;AAEnB,QADA,EAAO,KAAK,EACR,CAAC,EAAK,SAAU,QAAO,EAAO,mBAAmB;AACrD,QAAI,CAAC,EAAK,KAAK,MAAM,CAAE,QAAO,EAAO,+BAA+B;AACpE,MAAO,OACL;KACE,UAAU,EAAK;KACf,gBAAgB,EAAK,kBAAkB;KACvC,MAAM,EAAK,KAAK,MAAM;KACtB,aAAa,EAAK,YAAY,MAAM,IAAI;KACxC,gBAAgB,EAAK;KACrB,WAAW,EAAK,YAAY,OAAO,EAAK,UAAU,GAAG;KACrD,UAAU,EAAK,YAAY;KAC3B,WAAW,EAAK,YAAY,IAAI,KAAK,EAAK,UAAU,CAAC,aAAa,GAAG;KACtE,EACD;KACE,iBAAiB;AAEf,MADA,EAAQ,EAAK,KAAK,MAAM,CAAC,EACzB,GAAS;;KAEX,UAAU,MAAM,EAAO,aAAa,QAAQ,EAAE,UAAU,UAAU;KACnE,CACF;;GAa8B,UAAU,EAAO;aACvC,EAAO,YAAY,cAAc;GAC3B,CAAA,CACR,EAAA,CAAA;YAGL,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KAAO,kBAAC,GAAD;KAAQ,MAAK;eAAU;KAAa,CAAA;IAC5C,kBAAC,GAAD;KAAO,OAAM;KAAS,UAAA;eACpB,kBAAC,GAAD;MAAc,OAAO,EAAK;MAAU,UAAU,EAAI,WAAW;gBAA7D,CACE,kBAAC,UAAD;OAAQ,OAAM;iBAAG;OAAyB,CAAA,EACzC,EAAQ,MAAM,MAAM,KAAK,MACxB,kBAAC,UAAD;OAAmB,OAAO,EAAE;iBACzB,EAAE;OACI,EAFI,EAAE,GAEN,CACT,CACW;;KACT,CAAA;IACR,kBAAC,GAAD;KAAO,OAAM;KAAO,UAAA;eAClB,kBAAC,GAAD;MAAW,OAAO,EAAK;MAAM,UAAU,EAAI,OAAO;MAAE,aAAY;MAAyB,CAAA;KACnF,CAAA;IACR,kBAAC,GAAD;KAAO,OAAM;eACX,kBAAC,GAAD;MAAc,OAAO,EAAK;MAAgB,UAAU,EAAI,iBAAiB;gBAAzE,CACE,kBAAC,UAAD;OAAQ,OAAM;iBAAG;OAAmB,CAAA,EACnC,EAAc,MAAM,KAAK,MACxB,kBAAC,UAAD;OAAmB,OAAO,EAAE;iBACzB,EAAE;OACI,EAFI,EAAE,GAEN,CACT,CACW;;KACT,CAAA;IACR,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAO,OAAM;iBACX,kBAAC,GAAD;QAAc,OAAO,EAAK;QAAgB,UAAU,EAAI,iBAAiB;kBACtE,EAAY,EAAiB;QACjB,CAAA;OACT,CAAA;MACR,kBAAC,GAAD;OAAO,OAAM;iBACX,kBAAC,GAAD;QACE,MAAK;QACL,KAAI;QACJ,OAAO,EAAK;QACZ,UAAU,EAAI,YAAY;QAC1B,aAAY;QACZ,CAAA;OACI,CAAA;MACR,kBAAC,GAAD;OAAO,OAAM;iBACX,kBAAC,GAAD;QAAW,OAAO,EAAK;QAAU,UAAU,EAAI,WAAW;QAAE,WAAW;QAAK,CAAA;OACtE,CAAA;MACJ;;IACN,kBAAC,GAAD;KAAO,OAAM;eACX,kBAAC,GAAD;MAAW,MAAK;MAAO,OAAO,EAAK;MAAW,UAAU,EAAI,YAAY;MAAI,CAAA;KACtE,CAAA;IACR,kBAAC,GAAD;KAAO,OAAM;eACX,kBAAC,GAAD;MAAU,OAAO,EAAK;MAAa,UAAU,EAAI,cAAc;MAAI,CAAA;KAC7D,CAAA;IACJ;;EACA,CAAA"}
@@ -0,0 +1,303 @@
1
+ import { useClients as e, useCreateInvoice as t, useEngagements as n, useInvoices as r, useMarkInvoicePaid as i } from "../hooks/useProServiceWorldApi.js";
2
+ import { Banner as a, Empty as o, ErrorState as s, Field as c, LoadingBlock as l, MetricCard 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, fmtMoney as C } from "../components/ui.js";
3
+ import { useMemo as w, useState as T } from "react";
4
+ import { CheckCircle2 as E, Plus as D, Receipt as O } from "lucide-react";
5
+ import { Fragment as k, jsx as A, jsxs as j } from "react/jsx-runtime";
6
+ import { Button as M } from "@burdenoff/fe-libs/ui";
7
+ //#region src/proserviceworld/pages/InvoicesPage.tsx
8
+ var N = [
9
+ "DRAFT",
10
+ "SENT",
11
+ "PAID",
12
+ "OVERDUE",
13
+ "CANCELLED"
14
+ ], P = () => {
15
+ let [t, n] = T(""), [c, d] = T(!1), [v, y] = T(null), { data: k, isLoading: P, error: I, refetch: L } = r({ status: t || void 0 }), R = i(), z = e({ pagination: { limit: 100 } }), B = w(() => new Map((z.data?.items ?? []).map((e) => [e.id, e.name])), [z.data]), V = (k?.items ?? []).filter((e) => e.status === "SENT" || e.status === "OVERDUE").reduce((e, t) => e + t.total, 0), H = (k?.items ?? []).filter((e) => e.status === "PAID").reduce((e, t) => e + t.total, 0);
16
+ return /* @__PURE__ */ j("div", {
17
+ className: "space-y-6",
18
+ children: [
19
+ /* @__PURE__ */ A(p, {
20
+ title: "Invoices",
21
+ subtitle: "Assemble invoices from time, milestones and fixed-fee schedules — and track what's outstanding.",
22
+ icon: /* @__PURE__ */ A(O, { className: "h-6 w-6" }),
23
+ actions: /* @__PURE__ */ j(M, {
24
+ onClick: () => d(!0),
25
+ children: [/* @__PURE__ */ A(D, { className: "mr-1.5 h-4 w-4" }), "New invoice"]
26
+ })
27
+ }),
28
+ v && /* @__PURE__ */ A(a, {
29
+ tone: v.tone,
30
+ onClose: () => y(null),
31
+ children: v.text
32
+ }),
33
+ /* @__PURE__ */ j("div", {
34
+ className: "grid grid-cols-3 gap-3",
35
+ children: [
36
+ /* @__PURE__ */ A(u, {
37
+ label: "Invoices",
38
+ value: k?.totalCount ?? 0
39
+ }),
40
+ /* @__PURE__ */ A(u, {
41
+ label: "Outstanding",
42
+ value: C(V),
43
+ tone: "warning"
44
+ }),
45
+ /* @__PURE__ */ A(u, {
46
+ label: "Collected",
47
+ value: C(H),
48
+ tone: "success"
49
+ })
50
+ ]
51
+ }),
52
+ /* @__PURE__ */ A("div", {
53
+ className: "flex justify-end",
54
+ children: /* @__PURE__ */ j(f, {
55
+ value: t,
56
+ onChange: (e) => n(e.target.value),
57
+ className: "sm:w-48",
58
+ children: [/* @__PURE__ */ A("option", {
59
+ value: "",
60
+ children: "All statuses"
61
+ }), x(N)]
62
+ })
63
+ }),
64
+ P ? /* @__PURE__ */ A(l, {}) : I ? /* @__PURE__ */ A(s, {
65
+ error: I,
66
+ onRetry: () => void L()
67
+ }) : !k || k.items.length === 0 ? /* @__PURE__ */ A(o, {
68
+ title: "No invoices yet",
69
+ description: "Raise an invoice for a client to start tracking receivables.",
70
+ icon: /* @__PURE__ */ A(O, { className: "h-8 w-8" }),
71
+ action: /* @__PURE__ */ j(M, {
72
+ onClick: () => d(!0),
73
+ children: [/* @__PURE__ */ A(D, { className: "mr-1.5 h-4 w-4" }), "New invoice"]
74
+ })
75
+ }) : /* @__PURE__ */ A(m, {
76
+ title: "Invoices",
77
+ children: /* @__PURE__ */ j(g, { children: [/* @__PURE__ */ A("thead", { children: /* @__PURE__ */ j("tr", { children: [
78
+ /* @__PURE__ */ A(b, { children: "Number" }),
79
+ /* @__PURE__ */ A(b, { children: "Client" }),
80
+ /* @__PURE__ */ A(b, { children: "Total" }),
81
+ /* @__PURE__ */ A(b, { children: "Status" }),
82
+ /* @__PURE__ */ A(b, { children: "Due" }),
83
+ /* @__PURE__ */ A(b, {
84
+ className: "text-right",
85
+ children: "Actions"
86
+ })
87
+ ] }) }), /* @__PURE__ */ A("tbody", { children: k.items.map((e) => /* @__PURE__ */ j("tr", {
88
+ className: "hover:bg-bg-sunken/50",
89
+ children: [
90
+ /* @__PURE__ */ A(_, { children: /* @__PURE__ */ A("span", {
91
+ className: "font-medium text-text-primary",
92
+ children: e.number
93
+ }) }),
94
+ /* @__PURE__ */ A(_, { children: B.get(e.clientId) ?? "—" }),
95
+ /* @__PURE__ */ A(_, { children: C(e.total, e.currency) }),
96
+ /* @__PURE__ */ A(_, { children: /* @__PURE__ */ A(h, { status: e.status }) }),
97
+ /* @__PURE__ */ A(_, { children: S(e.dueDate) }),
98
+ /* @__PURE__ */ A(_, {
99
+ className: "text-right",
100
+ children: e.status !== "PAID" && e.status !== "CANCELLED" && /* @__PURE__ */ j(M, {
101
+ variant: "ghost",
102
+ size: "sm",
103
+ disabled: R.isPending,
104
+ onClick: () => R.mutate(e.id, {
105
+ onSuccess: () => y({
106
+ tone: "success",
107
+ text: `${e.number} marked paid.`
108
+ }),
109
+ onError: (e) => y({
110
+ tone: "danger",
111
+ text: e instanceof Error ? e.message : "Failed."
112
+ })
113
+ }),
114
+ children: [/* @__PURE__ */ A(E, { className: "mr-1 h-4 w-4" }), "Mark paid"]
115
+ })
116
+ })
117
+ ]
118
+ }, e.id)) })] })
119
+ }),
120
+ c && /* @__PURE__ */ A(F, {
121
+ onClose: () => d(!1),
122
+ onSaved: (e) => y({
123
+ tone: "success",
124
+ text: `${e} created.`
125
+ })
126
+ })
127
+ ]
128
+ });
129
+ }, F = ({ onClose: r, onSaved: i }) => {
130
+ let o = t(), s = e({ pagination: { limit: 100 } }), [l, u] = T({
131
+ clientId: "",
132
+ engagementId: "",
133
+ number: "",
134
+ currency: "USD",
135
+ subtotal: "",
136
+ taxAmount: "",
137
+ issueDate: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
138
+ dueDate: "",
139
+ notes: ""
140
+ }), [p, m] = T(null), h = n({
141
+ clientId: l.clientId || void 0,
142
+ pagination: { limit: 100 }
143
+ }), g = (e) => (t) => u((n) => ({
144
+ ...n,
145
+ [e]: t.target.value
146
+ })), _ = (Number(l.subtotal) || 0) + (Number(l.taxAmount) || 0);
147
+ return /* @__PURE__ */ A(d, {
148
+ open: !0,
149
+ onClose: r,
150
+ title: "New invoice",
151
+ footer: /* @__PURE__ */ j(k, { children: [/* @__PURE__ */ A(M, {
152
+ variant: "outline",
153
+ onClick: r,
154
+ disabled: o.isPending,
155
+ children: "Cancel"
156
+ }), /* @__PURE__ */ A(M, {
157
+ onClick: () => {
158
+ if (m(null), !l.clientId) return m("Select a client.");
159
+ if (!l.number.trim()) return m("Invoice number is required.");
160
+ o.mutate({
161
+ clientId: l.clientId,
162
+ engagementId: l.engagementId || null,
163
+ number: l.number.trim(),
164
+ currency: l.currency || "USD",
165
+ subtotal: Number(l.subtotal) || 0,
166
+ taxAmount: Number(l.taxAmount) || 0,
167
+ total: _,
168
+ issueDate: l.issueDate ? new Date(l.issueDate).toISOString() : null,
169
+ dueDate: l.dueDate ? new Date(l.dueDate).toISOString() : null,
170
+ notes: l.notes.trim() || null
171
+ }, {
172
+ onSuccess: () => {
173
+ i(l.number.trim()), r();
174
+ },
175
+ onError: (e) => m(e instanceof Error ? e.message : "Failed.")
176
+ });
177
+ },
178
+ disabled: o.isPending,
179
+ children: o.isPending ? "Creating…" : "Create invoice"
180
+ })] }),
181
+ children: /* @__PURE__ */ j("div", {
182
+ className: "space-y-3",
183
+ children: [
184
+ p && /* @__PURE__ */ A(a, {
185
+ tone: "danger",
186
+ children: p
187
+ }),
188
+ /* @__PURE__ */ j("div", {
189
+ className: "grid grid-cols-2 gap-3",
190
+ children: [/* @__PURE__ */ A(c, {
191
+ label: "Client",
192
+ required: !0,
193
+ children: /* @__PURE__ */ j(f, {
194
+ value: l.clientId,
195
+ onChange: g("clientId"),
196
+ children: [/* @__PURE__ */ A("option", {
197
+ value: "",
198
+ children: "Select…"
199
+ }), s.data?.items.map((e) => /* @__PURE__ */ A("option", {
200
+ value: e.id,
201
+ children: e.name
202
+ }, e.id))]
203
+ })
204
+ }), /* @__PURE__ */ A(c, {
205
+ label: "Number",
206
+ required: !0,
207
+ children: /* @__PURE__ */ A(y, {
208
+ value: l.number,
209
+ onChange: g("number"),
210
+ placeholder: "INV-2026-014"
211
+ })
212
+ })]
213
+ }),
214
+ /* @__PURE__ */ A(c, {
215
+ label: "Engagement (optional)",
216
+ children: /* @__PURE__ */ j(f, {
217
+ value: l.engagementId,
218
+ onChange: g("engagementId"),
219
+ disabled: !l.clientId,
220
+ children: [/* @__PURE__ */ A("option", {
221
+ value: "",
222
+ children: "None"
223
+ }), h.data?.items.map((e) => /* @__PURE__ */ A("option", {
224
+ value: e.id,
225
+ children: e.name
226
+ }, e.id))]
227
+ })
228
+ }),
229
+ /* @__PURE__ */ j("div", {
230
+ className: "grid grid-cols-3 gap-3",
231
+ children: [
232
+ /* @__PURE__ */ A(c, {
233
+ label: "Subtotal",
234
+ children: /* @__PURE__ */ A(y, {
235
+ type: "number",
236
+ min: "0",
237
+ value: l.subtotal,
238
+ onChange: g("subtotal")
239
+ })
240
+ }),
241
+ /* @__PURE__ */ A(c, {
242
+ label: "Tax",
243
+ children: /* @__PURE__ */ A(y, {
244
+ type: "number",
245
+ min: "0",
246
+ value: l.taxAmount,
247
+ onChange: g("taxAmount")
248
+ })
249
+ }),
250
+ /* @__PURE__ */ A(c, {
251
+ label: "Currency",
252
+ children: /* @__PURE__ */ A(y, {
253
+ value: l.currency,
254
+ onChange: g("currency"),
255
+ maxLength: 3
256
+ })
257
+ })
258
+ ]
259
+ }),
260
+ /* @__PURE__ */ j("div", {
261
+ className: "rounded-lg bg-bg-sunken px-3 py-2 text-sm text-text-secondary",
262
+ children: [
263
+ "Total:",
264
+ " ",
265
+ /* @__PURE__ */ A("span", {
266
+ className: "font-semibold text-text-primary",
267
+ children: C(_, l.currency)
268
+ })
269
+ ]
270
+ }),
271
+ /* @__PURE__ */ j("div", {
272
+ className: "grid grid-cols-2 gap-3",
273
+ children: [/* @__PURE__ */ A(c, {
274
+ label: "Issue date",
275
+ children: /* @__PURE__ */ A(y, {
276
+ type: "date",
277
+ value: l.issueDate,
278
+ onChange: g("issueDate")
279
+ })
280
+ }), /* @__PURE__ */ A(c, {
281
+ label: "Due date",
282
+ children: /* @__PURE__ */ A(y, {
283
+ type: "date",
284
+ value: l.dueDate,
285
+ onChange: g("dueDate")
286
+ })
287
+ })]
288
+ }),
289
+ /* @__PURE__ */ A(c, {
290
+ label: "Notes",
291
+ children: /* @__PURE__ */ A(v, {
292
+ value: l.notes,
293
+ onChange: g("notes")
294
+ })
295
+ })
296
+ ]
297
+ })
298
+ });
299
+ };
300
+ //#endregion
301
+ export { P as InvoicesPage };
302
+
303
+ //# sourceMappingURL=InvoicesPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InvoicesPage.js","names":[],"sources":["../../../src/proserviceworld/pages/InvoicesPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus, Receipt, CheckCircle2 } from 'lucide-react';\nimport {\n useInvoices,\n useCreateInvoice,\n useMarkInvoicePaid,\n useClients,\n useEngagements,\n type PswInvoiceStatus,\n} from '../hooks/useProServiceWorldApi';\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 MetricCard,\n TableWrap,\n Th,\n Td,\n enumOptions,\n fmtDate,\n fmtMoney,\n} from '../components/ui';\n\nconst STATUSES: PswInvoiceStatus[] = ['DRAFT', 'SENT', 'PAID', 'OVERDUE', 'CANCELLED'];\n\nexport const InvoicesPage: FC = () => {\n const [statusFilter, setStatusFilter] = useState<PswInvoiceStatus | ''>('');\n const [creating, setCreating] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n const { data, isLoading, error, refetch } = useInvoices({ status: statusFilter || undefined });\n const markPaid = useMarkInvoicePaid();\n const clients = useClients({ pagination: { limit: 100 } });\n const clientName = useMemo(\n () => new Map((clients.data?.items ?? []).map((c) => [c.id, c.name])),\n [clients.data]\n );\n\n const outstanding = (data?.items ?? [])\n .filter((i) => i.status === 'SENT' || i.status === 'OVERDUE')\n .reduce((s, i) => s + i.total, 0);\n const collected = (data?.items ?? [])\n .filter((i) => i.status === 'PAID')\n .reduce((s, i) => s + i.total, 0);\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Invoices\"\n subtitle=\"Assemble invoices from time, milestones and fixed-fee schedules — and track what's outstanding.\"\n icon={<Receipt className=\"h-6 w-6\" />}\n actions={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n New invoice\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=\"grid grid-cols-3 gap-3\">\n <MetricCard label=\"Invoices\" value={data?.totalCount ?? 0} />\n <MetricCard label=\"Outstanding\" value={fmtMoney(outstanding)} tone=\"warning\" />\n <MetricCard label=\"Collected\" value={fmtMoney(collected)} tone=\"success\" />\n </div>\n\n <div className=\"flex justify-end\">\n <NativeSelect\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as PswInvoiceStatus | '')}\n className=\"sm:w-48\"\n >\n <option value=\"\">All statuses</option>\n {enumOptions(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 invoices yet\"\n description=\"Raise an invoice for a client to start tracking receivables.\"\n icon={<Receipt className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n New invoice\n </Button>\n }\n />\n ) : (\n <SectionCard title=\"Invoices\">\n <TableWrap>\n <thead>\n <tr>\n <Th>Number</Th>\n <Th>Client</Th>\n <Th>Total</Th>\n <Th>Status</Th>\n <Th>Due</Th>\n <Th className=\"text-right\">Actions</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((i) => (\n <tr key={i.id} className=\"hover:bg-bg-sunken/50\">\n <Td>\n <span className=\"font-medium text-text-primary\">{i.number}</span>\n </Td>\n <Td>{clientName.get(i.clientId) ?? '—'}</Td>\n <Td>{fmtMoney(i.total, i.currency)}</Td>\n <Td>\n <StatusPill status={i.status} />\n </Td>\n <Td>{fmtDate(i.dueDate)}</Td>\n <Td className=\"text-right\">\n {i.status !== 'PAID' && i.status !== 'CANCELLED' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={markPaid.isPending}\n onClick={() =>\n markPaid.mutate(i.id, {\n onSuccess: () =>\n setBanner({ tone: 'success', text: `${i.number} marked paid.` }),\n onError: (e) =>\n setBanner({\n tone: 'danger',\n text: e instanceof Error ? e.message : 'Failed.',\n }),\n })\n }\n >\n <CheckCircle2 className=\"mr-1 h-4 w-4\" />\n Mark paid\n </Button>\n )}\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </SectionCard>\n )}\n\n {creating && (\n <InvoiceModal\n onClose={() => setCreating(false)}\n onSaved={(n) => setBanner({ tone: 'success', text: `${n} created.` })}\n />\n )}\n </div>\n );\n};\n\nconst InvoiceModal: FC<{ onClose: () => void; onSaved: (number: string) => void }> = ({\n onClose,\n onSaved,\n}) => {\n const create = useCreateInvoice();\n const clients = useClients({ pagination: { limit: 100 } });\n const [form, setForm] = useState({\n clientId: '',\n engagementId: '',\n number: '',\n currency: 'USD',\n subtotal: '',\n taxAmount: '',\n issueDate: new Date().toISOString().slice(0, 10),\n dueDate: '',\n notes: '',\n });\n const [err, setErr] = useState<string | null>(null);\n const engagements = useEngagements({\n clientId: form.clientId || undefined,\n pagination: { limit: 100 },\n });\n const set = (k: keyof typeof form) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n const total = (Number(form.subtotal) || 0) + (Number(form.taxAmount) || 0);\n\n const submit = () => {\n setErr(null);\n if (!form.clientId) return setErr('Select a client.');\n if (!form.number.trim()) return setErr('Invoice number is required.');\n create.mutate(\n {\n clientId: form.clientId,\n engagementId: form.engagementId || null,\n number: form.number.trim(),\n currency: form.currency || 'USD',\n subtotal: Number(form.subtotal) || 0,\n taxAmount: Number(form.taxAmount) || 0,\n total,\n issueDate: form.issueDate ? new Date(form.issueDate).toISOString() : null,\n dueDate: form.dueDate ? new Date(form.dueDate).toISOString() : null,\n notes: form.notes.trim() || null,\n },\n {\n onSuccess: () => {\n onSaved(form.number.trim());\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=\"New invoice\"\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 ? 'Creating…' : 'Create invoice'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Client\" required>\n <NativeSelect value={form.clientId} onChange={set('clientId')}>\n <option value=\"\">Select…</option>\n {clients.data?.items.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </NativeSelect>\n </Field>\n <Field label=\"Number\" required>\n <TextInput value={form.number} onChange={set('number')} placeholder=\"INV-2026-014\" />\n </Field>\n </div>\n <Field label=\"Engagement (optional)\">\n <NativeSelect\n value={form.engagementId}\n onChange={set('engagementId')}\n disabled={!form.clientId}\n >\n <option value=\"\">None</option>\n {engagements.data?.items.map((e) => (\n <option key={e.id} value={e.id}>\n {e.name}\n </option>\n ))}\n </NativeSelect>\n </Field>\n <div className=\"grid grid-cols-3 gap-3\">\n <Field label=\"Subtotal\">\n <TextInput type=\"number\" min=\"0\" value={form.subtotal} onChange={set('subtotal')} />\n </Field>\n <Field label=\"Tax\">\n <TextInput type=\"number\" min=\"0\" value={form.taxAmount} onChange={set('taxAmount')} />\n </Field>\n <Field label=\"Currency\">\n <TextInput value={form.currency} onChange={set('currency')} maxLength={3} />\n </Field>\n </div>\n <div className=\"rounded-lg bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n Total:{' '}\n <span className=\"font-semibold text-text-primary\">{fmtMoney(total, form.currency)}</span>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Issue date\">\n <TextInput type=\"date\" value={form.issueDate} onChange={set('issueDate')} />\n </Field>\n <Field label=\"Due date\">\n <TextInput type=\"date\" value={form.dueDate} onChange={set('dueDate')} />\n </Field>\n </div>\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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"}