@burdenoff/microfe-proserviceworld 2026.706.2 → 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.
- package/dist/proserviceworld/components/DiscussionThread.js +1 -1
- package/dist/proserviceworld/components/DiscussionThread.js.map +1 -1
- package/dist/proserviceworld/components/ui.js +18 -18
- package/dist/proserviceworld/components/ui.js.map +1 -1
- package/dist/proserviceworld/hooks/useEntityComments.js.map +1 -1
- package/dist/proserviceworld/pages/AnalyticsPage.js +277 -111
- package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ClientDetailPage.js +219 -151
- package/dist/proserviceworld/pages/ClientDetailPage.js.map +1 -1
- package/dist/proserviceworld/pages/ClientsPage.js +148 -127
- package/dist/proserviceworld/pages/ClientsPage.js.map +1 -1
- package/dist/proserviceworld/pages/CompliancePage.js +140 -96
- package/dist/proserviceworld/pages/CompliancePage.js.map +1 -1
- package/dist/proserviceworld/pages/DashboardPage.js +221 -104
- package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
- package/dist/proserviceworld/pages/EngagementDetailPage.js +211 -181
- package/dist/proserviceworld/pages/EngagementDetailPage.js.map +1 -1
- package/dist/proserviceworld/pages/EngagementsPage.js +144 -106
- package/dist/proserviceworld/pages/EngagementsPage.js.map +1 -1
- package/dist/proserviceworld/pages/InvoicesPage.js +144 -129
- package/dist/proserviceworld/pages/InvoicesPage.js.map +1 -1
- package/dist/proserviceworld/pages/MattersPage.js +163 -122
- package/dist/proserviceworld/pages/MattersPage.js.map +1 -1
- package/dist/proserviceworld/pages/TimePage.js +112 -92
- package/dist/proserviceworld/pages/TimePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,133 +1,148 @@
|
|
|
1
1
|
import { useClients as e, useCreateInvoice as t, useEngagements as n, useInvoices as r, useMarkInvoicePaid as i } from "../hooks/useProServiceWorldApi.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { cn as a } from "../utils/cn.js";
|
|
3
|
+
import { Banner as o, Empty as s, ErrorState as c, Field as l, LoadingBlock as u, MetricCard as d, Modal as f, NativeSelect as p, PageHeader as m, SectionCard as h, StatusPill as g, TableWrap as _, Td as v, TextArea as y, TextInput as b, Th as x, enumOptions as S, fmtDate as C, fmtMoney as w } from "../components/ui.js";
|
|
4
|
+
import { useMemo as T, useState as E } from "react";
|
|
5
|
+
import { AlertCircle as D, CheckCircle2 as O, Plus as k, Receipt as A, Wallet as j } from "lucide-react";
|
|
6
|
+
import { Fragment as M, jsx as N, jsxs as P } from "react/jsx-runtime";
|
|
7
|
+
import { Button as F } from "@burdenoff/fe-libs/ui";
|
|
7
8
|
//#region src/proserviceworld/pages/InvoicesPage.tsx
|
|
8
|
-
var
|
|
9
|
+
var I = [
|
|
9
10
|
"DRAFT",
|
|
10
11
|
"SENT",
|
|
11
12
|
"PAID",
|
|
12
13
|
"OVERDUE",
|
|
13
14
|
"CANCELLED"
|
|
14
|
-
],
|
|
15
|
-
let [t, n] =
|
|
16
|
-
return /* @__PURE__ */
|
|
15
|
+
], L = () => {
|
|
16
|
+
let [t, n] = E(""), [l, f] = E(!1), [y, b] = E(null), { data: M, isLoading: L, error: z, refetch: B } = r({ status: t || void 0 }), V = i(), H = e({ pagination: { limit: 100 } }), U = T(() => new Map((H.data?.items ?? []).map((e) => [e.id, e.name])), [H.data]), W = (M?.items ?? []).filter((e) => e.status === "SENT" || e.status === "OVERDUE"), G = (M?.items ?? []).filter((e) => e.status === "PAID"), K = W.reduce((e, t) => e + t.total, 0), q = G.reduce((e, t) => e + t.total, 0);
|
|
17
|
+
return /* @__PURE__ */ P("div", {
|
|
17
18
|
className: "space-y-6",
|
|
18
19
|
children: [
|
|
19
|
-
/* @__PURE__ */
|
|
20
|
+
/* @__PURE__ */ N(m, {
|
|
20
21
|
title: "Invoices",
|
|
21
22
|
subtitle: "Assemble invoices from time, milestones and fixed-fee schedules — and track what's outstanding.",
|
|
22
|
-
icon: /* @__PURE__ */ A
|
|
23
|
-
actions: /* @__PURE__ */
|
|
24
|
-
onClick: () =>
|
|
25
|
-
children: [/* @__PURE__ */
|
|
23
|
+
icon: /* @__PURE__ */ N(A, { className: "h-6 w-6" }),
|
|
24
|
+
actions: /* @__PURE__ */ P(F, {
|
|
25
|
+
onClick: () => f(!0),
|
|
26
|
+
children: [/* @__PURE__ */ N(k, { className: "mr-1.5 h-4 w-4" }), "New invoice"]
|
|
26
27
|
})
|
|
27
28
|
}),
|
|
28
|
-
|
|
29
|
-
tone:
|
|
30
|
-
onClose: () =>
|
|
31
|
-
children:
|
|
29
|
+
y && /* @__PURE__ */ N(o, {
|
|
30
|
+
tone: y.tone,
|
|
31
|
+
onClose: () => b(null),
|
|
32
|
+
children: y.text
|
|
32
33
|
}),
|
|
33
|
-
/* @__PURE__ */
|
|
34
|
-
className: "grid grid-cols-
|
|
34
|
+
/* @__PURE__ */ P("div", {
|
|
35
|
+
className: "grid grid-cols-1 gap-3 sm:grid-cols-3",
|
|
35
36
|
children: [
|
|
36
|
-
/* @__PURE__ */
|
|
37
|
+
/* @__PURE__ */ N(d, {
|
|
37
38
|
label: "Invoices",
|
|
38
|
-
value:
|
|
39
|
+
value: M?.totalCount ?? 0,
|
|
40
|
+
icon: /* @__PURE__ */ N(A, { className: "h-4 w-4" })
|
|
39
41
|
}),
|
|
40
|
-
/* @__PURE__ */
|
|
42
|
+
/* @__PURE__ */ N(d, {
|
|
41
43
|
label: "Outstanding",
|
|
42
|
-
value:
|
|
44
|
+
value: w(K),
|
|
45
|
+
hint: `${W.length} sent + overdue`,
|
|
46
|
+
icon: /* @__PURE__ */ N(D, { className: "h-4 w-4" }),
|
|
43
47
|
tone: "warning"
|
|
44
48
|
}),
|
|
45
|
-
/* @__PURE__ */
|
|
49
|
+
/* @__PURE__ */ N(d, {
|
|
46
50
|
label: "Collected",
|
|
47
|
-
value:
|
|
51
|
+
value: w(q),
|
|
52
|
+
hint: `${G.length} paid`,
|
|
53
|
+
icon: /* @__PURE__ */ N(j, { className: "h-4 w-4" }),
|
|
48
54
|
tone: "success"
|
|
49
55
|
})
|
|
50
56
|
]
|
|
51
57
|
}),
|
|
52
|
-
/* @__PURE__ */
|
|
58
|
+
/* @__PURE__ */ N("div", {
|
|
53
59
|
className: "flex justify-end",
|
|
54
|
-
children: /* @__PURE__ */
|
|
60
|
+
children: /* @__PURE__ */ P(p, {
|
|
55
61
|
value: t,
|
|
56
62
|
onChange: (e) => n(e.target.value),
|
|
57
63
|
className: "sm:w-48",
|
|
58
|
-
children: [/* @__PURE__ */
|
|
64
|
+
children: [/* @__PURE__ */ N("option", {
|
|
59
65
|
value: "",
|
|
60
66
|
children: "All statuses"
|
|
61
|
-
}),
|
|
67
|
+
}), S(I)]
|
|
62
68
|
})
|
|
63
69
|
}),
|
|
64
|
-
|
|
65
|
-
error:
|
|
66
|
-
onRetry: () => void
|
|
67
|
-
}) : !
|
|
70
|
+
L ? /* @__PURE__ */ N(u, {}) : z ? /* @__PURE__ */ N(c, {
|
|
71
|
+
error: z,
|
|
72
|
+
onRetry: () => void B()
|
|
73
|
+
}) : !M || M.items.length === 0 ? /* @__PURE__ */ N(s, {
|
|
68
74
|
title: "No invoices yet",
|
|
69
75
|
description: "Raise an invoice for a client to start tracking receivables.",
|
|
70
|
-
icon: /* @__PURE__ */ A
|
|
71
|
-
action: /* @__PURE__ */
|
|
72
|
-
onClick: () =>
|
|
73
|
-
children: [/* @__PURE__ */
|
|
76
|
+
icon: /* @__PURE__ */ N(A, { className: "h-8 w-8" }),
|
|
77
|
+
action: /* @__PURE__ */ P(F, {
|
|
78
|
+
onClick: () => f(!0),
|
|
79
|
+
children: [/* @__PURE__ */ N(k, { className: "mr-1.5 h-4 w-4" }), "New invoice"]
|
|
74
80
|
})
|
|
75
|
-
}) : /* @__PURE__ */
|
|
81
|
+
}) : /* @__PURE__ */ N(h, {
|
|
76
82
|
title: "Invoices",
|
|
77
|
-
children: /* @__PURE__ */
|
|
78
|
-
/* @__PURE__ */
|
|
79
|
-
/* @__PURE__ */
|
|
80
|
-
/* @__PURE__ */
|
|
81
|
-
/* @__PURE__ */
|
|
82
|
-
/* @__PURE__ */
|
|
83
|
-
/* @__PURE__ */
|
|
83
|
+
children: /* @__PURE__ */ P(_, { children: [/* @__PURE__ */ N("thead", { children: /* @__PURE__ */ P("tr", { children: [
|
|
84
|
+
/* @__PURE__ */ N(x, { children: "Number" }),
|
|
85
|
+
/* @__PURE__ */ N(x, { children: "Client" }),
|
|
86
|
+
/* @__PURE__ */ N(x, { children: "Total" }),
|
|
87
|
+
/* @__PURE__ */ N(x, { children: "Status" }),
|
|
88
|
+
/* @__PURE__ */ N(x, { children: "Due" }),
|
|
89
|
+
/* @__PURE__ */ N(x, {
|
|
84
90
|
className: "text-right",
|
|
85
91
|
children: "Actions"
|
|
86
92
|
})
|
|
87
|
-
] }) }), /* @__PURE__ */
|
|
88
|
-
className: "hover:bg-bg-sunken/
|
|
93
|
+
] }) }), /* @__PURE__ */ N("tbody", { children: M.items.map((e) => /* @__PURE__ */ P("tr", {
|
|
94
|
+
className: "transition-colors hover:bg-bg-sunken/60",
|
|
89
95
|
children: [
|
|
90
|
-
/* @__PURE__ */
|
|
96
|
+
/* @__PURE__ */ N(v, { children: /* @__PURE__ */ N("span", {
|
|
91
97
|
className: "font-medium text-text-primary",
|
|
92
98
|
children: e.number
|
|
93
99
|
}) }),
|
|
94
|
-
/* @__PURE__ */
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
/* @__PURE__ */
|
|
100
|
+
/* @__PURE__ */ N(v, {
|
|
101
|
+
className: "text-text-primary",
|
|
102
|
+
children: U.get(e.clientId) ?? "—"
|
|
103
|
+
}),
|
|
104
|
+
/* @__PURE__ */ N(v, {
|
|
105
|
+
className: "whitespace-nowrap font-semibold text-text-primary",
|
|
106
|
+
children: w(e.total, e.currency)
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ N(v, { children: /* @__PURE__ */ N(g, { status: e.status }) }),
|
|
109
|
+
/* @__PURE__ */ N(v, {
|
|
110
|
+
className: a("whitespace-nowrap", e.status === "OVERDUE" && "font-medium text-status-error-text"),
|
|
111
|
+
children: C(e.dueDate)
|
|
112
|
+
}),
|
|
113
|
+
/* @__PURE__ */ N(v, {
|
|
99
114
|
className: "text-right",
|
|
100
|
-
children: e.status !== "PAID" && e.status !== "CANCELLED" && /* @__PURE__ */
|
|
115
|
+
children: e.status !== "PAID" && e.status !== "CANCELLED" && /* @__PURE__ */ P(F, {
|
|
101
116
|
variant: "ghost",
|
|
102
117
|
size: "sm",
|
|
103
|
-
disabled:
|
|
104
|
-
onClick: () =>
|
|
105
|
-
onSuccess: () =>
|
|
118
|
+
disabled: V.isPending,
|
|
119
|
+
onClick: () => V.mutate(e.id, {
|
|
120
|
+
onSuccess: () => b({
|
|
106
121
|
tone: "success",
|
|
107
122
|
text: `${e.number} marked paid.`
|
|
108
123
|
}),
|
|
109
|
-
onError: (e) =>
|
|
124
|
+
onError: (e) => b({
|
|
110
125
|
tone: "danger",
|
|
111
126
|
text: e instanceof Error ? e.message : "Failed."
|
|
112
127
|
})
|
|
113
128
|
}),
|
|
114
|
-
children: [/* @__PURE__ */
|
|
129
|
+
children: [/* @__PURE__ */ N(O, { className: "mr-1 h-4 w-4" }), "Mark paid"]
|
|
115
130
|
})
|
|
116
131
|
})
|
|
117
132
|
]
|
|
118
133
|
}, e.id)) })] })
|
|
119
134
|
}),
|
|
120
|
-
|
|
121
|
-
onClose: () =>
|
|
122
|
-
onSaved: (e) =>
|
|
135
|
+
l && /* @__PURE__ */ N(R, {
|
|
136
|
+
onClose: () => f(!1),
|
|
137
|
+
onSaved: (e) => b({
|
|
123
138
|
tone: "success",
|
|
124
139
|
text: `${e} created.`
|
|
125
140
|
})
|
|
126
141
|
})
|
|
127
142
|
]
|
|
128
143
|
});
|
|
129
|
-
},
|
|
130
|
-
let
|
|
144
|
+
}, R = ({ onClose: r, onSaved: i }) => {
|
|
145
|
+
let a = t(), s = e({ pagination: { limit: 100 } }), [c, u] = E({
|
|
131
146
|
clientId: "",
|
|
132
147
|
engagementId: "",
|
|
133
148
|
number: "",
|
|
@@ -137,159 +152,159 @@ var N = [
|
|
|
137
152
|
issueDate: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
138
153
|
dueDate: "",
|
|
139
154
|
notes: ""
|
|
140
|
-
}), [
|
|
141
|
-
clientId:
|
|
155
|
+
}), [d, m] = E(null), h = n({
|
|
156
|
+
clientId: c.clientId || void 0,
|
|
142
157
|
pagination: { limit: 100 }
|
|
143
158
|
}), g = (e) => (t) => u((n) => ({
|
|
144
159
|
...n,
|
|
145
160
|
[e]: t.target.value
|
|
146
|
-
})), _ = (Number(
|
|
147
|
-
return /* @__PURE__ */
|
|
161
|
+
})), _ = (Number(c.subtotal) || 0) + (Number(c.taxAmount) || 0);
|
|
162
|
+
return /* @__PURE__ */ N(f, {
|
|
148
163
|
open: !0,
|
|
149
164
|
onClose: r,
|
|
150
165
|
title: "New invoice",
|
|
151
|
-
footer: /* @__PURE__ */
|
|
166
|
+
footer: /* @__PURE__ */ P(M, { children: [/* @__PURE__ */ N(F, {
|
|
152
167
|
variant: "outline",
|
|
153
168
|
onClick: r,
|
|
154
|
-
disabled:
|
|
169
|
+
disabled: a.isPending,
|
|
155
170
|
children: "Cancel"
|
|
156
|
-
}), /* @__PURE__ */
|
|
171
|
+
}), /* @__PURE__ */ N(F, {
|
|
157
172
|
onClick: () => {
|
|
158
|
-
if (m(null), !
|
|
159
|
-
if (!
|
|
160
|
-
|
|
161
|
-
clientId:
|
|
162
|
-
engagementId:
|
|
163
|
-
number:
|
|
164
|
-
currency:
|
|
165
|
-
subtotal: Number(
|
|
166
|
-
taxAmount: Number(
|
|
173
|
+
if (m(null), !c.clientId) return m("Select a client.");
|
|
174
|
+
if (!c.number.trim()) return m("Invoice number is required.");
|
|
175
|
+
a.mutate({
|
|
176
|
+
clientId: c.clientId,
|
|
177
|
+
engagementId: c.engagementId || null,
|
|
178
|
+
number: c.number.trim(),
|
|
179
|
+
currency: c.currency || "USD",
|
|
180
|
+
subtotal: Number(c.subtotal) || 0,
|
|
181
|
+
taxAmount: Number(c.taxAmount) || 0,
|
|
167
182
|
total: _,
|
|
168
|
-
issueDate:
|
|
169
|
-
dueDate:
|
|
170
|
-
notes:
|
|
183
|
+
issueDate: c.issueDate ? new Date(c.issueDate).toISOString() : null,
|
|
184
|
+
dueDate: c.dueDate ? new Date(c.dueDate).toISOString() : null,
|
|
185
|
+
notes: c.notes.trim() || null
|
|
171
186
|
}, {
|
|
172
187
|
onSuccess: () => {
|
|
173
|
-
i(
|
|
188
|
+
i(c.number.trim()), r();
|
|
174
189
|
},
|
|
175
190
|
onError: (e) => m(e instanceof Error ? e.message : "Failed.")
|
|
176
191
|
});
|
|
177
192
|
},
|
|
178
|
-
disabled:
|
|
179
|
-
children:
|
|
193
|
+
disabled: a.isPending,
|
|
194
|
+
children: a.isPending ? "Creating…" : "Create invoice"
|
|
180
195
|
})] }),
|
|
181
|
-
children: /* @__PURE__ */
|
|
196
|
+
children: /* @__PURE__ */ P("div", {
|
|
182
197
|
className: "space-y-3",
|
|
183
198
|
children: [
|
|
184
|
-
|
|
199
|
+
d && /* @__PURE__ */ N(o, {
|
|
185
200
|
tone: "danger",
|
|
186
|
-
children:
|
|
201
|
+
children: d
|
|
187
202
|
}),
|
|
188
|
-
/* @__PURE__ */
|
|
203
|
+
/* @__PURE__ */ P("div", {
|
|
189
204
|
className: "grid grid-cols-2 gap-3",
|
|
190
|
-
children: [/* @__PURE__ */
|
|
205
|
+
children: [/* @__PURE__ */ N(l, {
|
|
191
206
|
label: "Client",
|
|
192
207
|
required: !0,
|
|
193
|
-
children: /* @__PURE__ */
|
|
194
|
-
value:
|
|
208
|
+
children: /* @__PURE__ */ P(p, {
|
|
209
|
+
value: c.clientId,
|
|
195
210
|
onChange: g("clientId"),
|
|
196
|
-
children: [/* @__PURE__ */
|
|
211
|
+
children: [/* @__PURE__ */ N("option", {
|
|
197
212
|
value: "",
|
|
198
213
|
children: "Select…"
|
|
199
|
-
}), s.data?.items.map((e) => /* @__PURE__ */
|
|
214
|
+
}), s.data?.items.map((e) => /* @__PURE__ */ N("option", {
|
|
200
215
|
value: e.id,
|
|
201
216
|
children: e.name
|
|
202
217
|
}, e.id))]
|
|
203
218
|
})
|
|
204
|
-
}), /* @__PURE__ */
|
|
219
|
+
}), /* @__PURE__ */ N(l, {
|
|
205
220
|
label: "Number",
|
|
206
221
|
required: !0,
|
|
207
|
-
children: /* @__PURE__ */
|
|
208
|
-
value:
|
|
222
|
+
children: /* @__PURE__ */ N(b, {
|
|
223
|
+
value: c.number,
|
|
209
224
|
onChange: g("number"),
|
|
210
225
|
placeholder: "INV-2026-014"
|
|
211
226
|
})
|
|
212
227
|
})]
|
|
213
228
|
}),
|
|
214
|
-
/* @__PURE__ */
|
|
229
|
+
/* @__PURE__ */ N(l, {
|
|
215
230
|
label: "Engagement (optional)",
|
|
216
|
-
children: /* @__PURE__ */
|
|
217
|
-
value:
|
|
231
|
+
children: /* @__PURE__ */ P(p, {
|
|
232
|
+
value: c.engagementId,
|
|
218
233
|
onChange: g("engagementId"),
|
|
219
|
-
disabled: !
|
|
220
|
-
children: [/* @__PURE__ */
|
|
234
|
+
disabled: !c.clientId,
|
|
235
|
+
children: [/* @__PURE__ */ N("option", {
|
|
221
236
|
value: "",
|
|
222
237
|
children: "None"
|
|
223
|
-
}), h.data?.items.map((e) => /* @__PURE__ */
|
|
238
|
+
}), h.data?.items.map((e) => /* @__PURE__ */ N("option", {
|
|
224
239
|
value: e.id,
|
|
225
240
|
children: e.name
|
|
226
241
|
}, e.id))]
|
|
227
242
|
})
|
|
228
243
|
}),
|
|
229
|
-
/* @__PURE__ */
|
|
244
|
+
/* @__PURE__ */ P("div", {
|
|
230
245
|
className: "grid grid-cols-3 gap-3",
|
|
231
246
|
children: [
|
|
232
|
-
/* @__PURE__ */
|
|
247
|
+
/* @__PURE__ */ N(l, {
|
|
233
248
|
label: "Subtotal",
|
|
234
|
-
children: /* @__PURE__ */
|
|
249
|
+
children: /* @__PURE__ */ N(b, {
|
|
235
250
|
type: "number",
|
|
236
251
|
min: "0",
|
|
237
|
-
value:
|
|
252
|
+
value: c.subtotal,
|
|
238
253
|
onChange: g("subtotal")
|
|
239
254
|
})
|
|
240
255
|
}),
|
|
241
|
-
/* @__PURE__ */
|
|
256
|
+
/* @__PURE__ */ N(l, {
|
|
242
257
|
label: "Tax",
|
|
243
|
-
children: /* @__PURE__ */
|
|
258
|
+
children: /* @__PURE__ */ N(b, {
|
|
244
259
|
type: "number",
|
|
245
260
|
min: "0",
|
|
246
|
-
value:
|
|
261
|
+
value: c.taxAmount,
|
|
247
262
|
onChange: g("taxAmount")
|
|
248
263
|
})
|
|
249
264
|
}),
|
|
250
|
-
/* @__PURE__ */
|
|
265
|
+
/* @__PURE__ */ N(l, {
|
|
251
266
|
label: "Currency",
|
|
252
|
-
children: /* @__PURE__ */
|
|
253
|
-
value:
|
|
267
|
+
children: /* @__PURE__ */ N(b, {
|
|
268
|
+
value: c.currency,
|
|
254
269
|
onChange: g("currency"),
|
|
255
270
|
maxLength: 3
|
|
256
271
|
})
|
|
257
272
|
})
|
|
258
273
|
]
|
|
259
274
|
}),
|
|
260
|
-
/* @__PURE__ */
|
|
275
|
+
/* @__PURE__ */ P("div", {
|
|
261
276
|
className: "rounded-lg bg-bg-sunken px-3 py-2 text-sm text-text-secondary",
|
|
262
277
|
children: [
|
|
263
278
|
"Total:",
|
|
264
279
|
" ",
|
|
265
|
-
/* @__PURE__ */
|
|
280
|
+
/* @__PURE__ */ N("span", {
|
|
266
281
|
className: "font-semibold text-text-primary",
|
|
267
|
-
children:
|
|
282
|
+
children: w(_, c.currency)
|
|
268
283
|
})
|
|
269
284
|
]
|
|
270
285
|
}),
|
|
271
|
-
/* @__PURE__ */
|
|
286
|
+
/* @__PURE__ */ P("div", {
|
|
272
287
|
className: "grid grid-cols-2 gap-3",
|
|
273
|
-
children: [/* @__PURE__ */
|
|
288
|
+
children: [/* @__PURE__ */ N(l, {
|
|
274
289
|
label: "Issue date",
|
|
275
|
-
children: /* @__PURE__ */
|
|
290
|
+
children: /* @__PURE__ */ N(b, {
|
|
276
291
|
type: "date",
|
|
277
|
-
value:
|
|
292
|
+
value: c.issueDate,
|
|
278
293
|
onChange: g("issueDate")
|
|
279
294
|
})
|
|
280
|
-
}), /* @__PURE__ */
|
|
295
|
+
}), /* @__PURE__ */ N(l, {
|
|
281
296
|
label: "Due date",
|
|
282
|
-
children: /* @__PURE__ */
|
|
297
|
+
children: /* @__PURE__ */ N(b, {
|
|
283
298
|
type: "date",
|
|
284
|
-
value:
|
|
299
|
+
value: c.dueDate,
|
|
285
300
|
onChange: g("dueDate")
|
|
286
301
|
})
|
|
287
302
|
})]
|
|
288
303
|
}),
|
|
289
|
-
/* @__PURE__ */
|
|
304
|
+
/* @__PURE__ */ N(l, {
|
|
290
305
|
label: "Notes",
|
|
291
|
-
children: /* @__PURE__ */
|
|
292
|
-
value:
|
|
306
|
+
children: /* @__PURE__ */ N(y, {
|
|
307
|
+
value: c.notes,
|
|
293
308
|
onChange: g("notes")
|
|
294
309
|
})
|
|
295
310
|
})
|
|
@@ -298,6 +313,6 @@ var N = [
|
|
|
298
313
|
});
|
|
299
314
|
};
|
|
300
315
|
//#endregion
|
|
301
|
-
export {
|
|
316
|
+
export { L as InvoicesPage };
|
|
302
317
|
|
|
303
318
|
//# sourceMappingURL=InvoicesPage.js.map
|
|
@@ -1 +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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"}
|
|
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, AlertCircle, Wallet } 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';\nimport { cn } from '../utils/cn';\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 outstandingInvoices = (data?.items ?? []).filter(\n (i) => i.status === 'SENT' || i.status === 'OVERDUE'\n );\n const paidInvoices = (data?.items ?? []).filter((i) => i.status === 'PAID');\n const outstanding = outstandingInvoices.reduce((s, i) => s + i.total, 0);\n const collected = paidInvoices.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-1 gap-3 sm:grid-cols-3\">\n <MetricCard\n label=\"Invoices\"\n value={data?.totalCount ?? 0}\n icon={<Receipt className=\"h-4 w-4\" />}\n />\n <MetricCard\n label=\"Outstanding\"\n value={fmtMoney(outstanding)}\n hint={`${outstandingInvoices.length} sent + overdue`}\n icon={<AlertCircle className=\"h-4 w-4\" />}\n tone=\"warning\"\n />\n <MetricCard\n label=\"Collected\"\n value={fmtMoney(collected)}\n hint={`${paidInvoices.length} paid`}\n icon={<Wallet className=\"h-4 w-4\" />}\n tone=\"success\"\n />\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=\"transition-colors hover:bg-bg-sunken/60\">\n <Td>\n <span className=\"font-medium text-text-primary\">{i.number}</span>\n </Td>\n <Td className=\"text-text-primary\">{clientName.get(i.clientId) ?? '—'}</Td>\n <Td className=\"whitespace-nowrap font-semibold text-text-primary\">\n {fmtMoney(i.total, i.currency)}\n </Td>\n <Td>\n <StatusPill status={i.status} />\n </Td>\n <Td\n className={cn(\n 'whitespace-nowrap',\n i.status === 'OVERDUE' && 'font-medium text-status-error-text'\n )}\n >\n {fmtDate(i.dueDate)}\n </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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"}
|