@burdenoff/microfe-timecampus 2026.719.4 → 2026.731.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.
- package/dist/generated/wspace-operations.d.ts +527 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-operations.js +1112 -880
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.d.ts +175 -0
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/generated/wspace-types.js +5 -3
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/index.js +5 -5
- package/dist/timecampus/TimeCampusRoot.d.ts.map +1 -1
- package/dist/timecampus/TimeCampusRoot.js +81 -73
- package/dist/timecampus/TimeCampusRoot.js.map +1 -1
- package/dist/timecampus/TimeCampusRoutes.d.ts.map +1 -1
- package/dist/timecampus/TimeCampusRoutes.js +17 -13
- package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
- package/dist/timecampus/components/StatusBadge.d.ts.map +1 -1
- package/dist/timecampus/components/StatusBadge.js.map +1 -1
- package/dist/timecampus/pages/invoicing/InvoicingPage.d.ts +2 -0
- package/dist/timecampus/pages/invoicing/InvoicingPage.d.ts.map +1 -0
- package/dist/timecampus/pages/invoicing/InvoicingPage.js +595 -0
- package/dist/timecampus/pages/invoicing/InvoicingPage.js.map +1 -0
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.d.ts.map +1 -1
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js +88 -59
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js.map +1 -1
- package/dist/timecampus/types/index.d.ts.map +1 -1
- package/dist/timecampus/utils/helpers.d.ts.map +1 -1
- package/dist/timecampus/utils/helpers.js +3 -1
- package/dist/timecampus/utils/helpers.js.map +1 -1
- package/dist/types/assistant.d.ts.map +1 -1
- package/dist/utils/pageContext.d.ts.map +1 -1
- package/dist/utils/pageContext.js +1 -0
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
|
|
2
|
+
import { formatCurrency as t, formatDate as n } from "../../utils/helpers.js";
|
|
3
|
+
import { StatCard as r } from "../../components/StatCard.js";
|
|
4
|
+
import { LoadingSkeleton as i } from "../../components/LoadingSkeleton.js";
|
|
5
|
+
import { useDeleteInvoiceDraftMutation as a, useGenerateInvoiceDraftMutation as o, useGetInvoiceStatsQuery as ee, useListInvoicesQuery as s, useUpdateInvoiceStatusMutation as te } from "../../../generated/wspace-operations.js";
|
|
6
|
+
import { PageLayout as c } from "../../components/PageLayout.js";
|
|
7
|
+
import { StatusBadge as ne } from "../../components/StatusBadge.js";
|
|
8
|
+
import { EmptyState as re } from "../../components/EmptyState.js";
|
|
9
|
+
import { Modal as l } from "../../components/Modal.js";
|
|
10
|
+
import { ToastContainer as ie, useToast as ae } from "../../components/Toast.js";
|
|
11
|
+
import { TimeCampusInvoiceStatus as u } from "../../../generated/wspace-types.js";
|
|
12
|
+
import { useState as d } from "react";
|
|
13
|
+
import { AlertTriangle as oe, CheckCircle2 as se, DollarSign as ce, Download as f, Eye as le, FileText as p, Plus as ue, Send as de, Trash2 as fe } from "lucide-react";
|
|
14
|
+
import { gql as m } from "@apollo/client";
|
|
15
|
+
import { useApolloClient as h } from "@apollo/client/react";
|
|
16
|
+
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
17
|
+
//#region src/timecampus/pages/invoicing/InvoicingPage.tsx
|
|
18
|
+
var y = 25, b = "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary", pe = m`
|
|
19
|
+
query TimeCampusInvoicing_GetInvoiceHtml($id: ID!, $workspaceId: ID!) {
|
|
20
|
+
timeCampusInvoiceHtml(id: $id, workspaceId: $workspaceId)
|
|
21
|
+
}
|
|
22
|
+
`, me = m`
|
|
23
|
+
query TimeCampusInvoicing_GetInvoicePdf($id: ID!, $workspaceId: ID!) {
|
|
24
|
+
timeCampusInvoicePdf(id: $id, workspaceId: $workspaceId)
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
function x() {
|
|
28
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
29
|
+
}
|
|
30
|
+
function S() {
|
|
31
|
+
let e = /* @__PURE__ */ new Date();
|
|
32
|
+
return new Date(e.getFullYear(), e.getMonth(), 1).toISOString().slice(0, 10);
|
|
33
|
+
}
|
|
34
|
+
var C = {
|
|
35
|
+
clientName: "",
|
|
36
|
+
clientEmail: "",
|
|
37
|
+
clientAddress: "",
|
|
38
|
+
periodStart: S(),
|
|
39
|
+
periodEnd: x(),
|
|
40
|
+
taxRatePercent: "0",
|
|
41
|
+
currency: "USD",
|
|
42
|
+
dueDate: "",
|
|
43
|
+
notes: "",
|
|
44
|
+
projectIds: ""
|
|
45
|
+
};
|
|
46
|
+
function he(e, t) {
|
|
47
|
+
let n = atob(e), r = new Uint8Array(n.length);
|
|
48
|
+
for (let e = 0; e < n.length; e++) r[e] = n.charCodeAt(e);
|
|
49
|
+
let i = new Blob([r], { type: "application/pdf" }), a = URL.createObjectURL(i), o = document.createElement("a");
|
|
50
|
+
o.href = a, o.download = t, document.body.appendChild(o), o.click(), o.remove(), URL.revokeObjectURL(a);
|
|
51
|
+
}
|
|
52
|
+
function w() {
|
|
53
|
+
let { workspaceId: m = "" } = e(), { toasts: w, addToast: T, dismissToast: ge } = ae(), E = h(), { data: D, loading: O, error: _e, refetch: ve, fetchMore: ye } = s({
|
|
54
|
+
variables: {
|
|
55
|
+
workspaceId: m,
|
|
56
|
+
pagination: {
|
|
57
|
+
limit: y,
|
|
58
|
+
offset: 0
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
notifyOnNetworkStatusChange: !0
|
|
62
|
+
}), { data: be, loading: xe, refetch: Se } = ee({ variables: { workspaceId: m } }), [Ce, { loading: k }] = o(), [A] = te(), [we, { loading: j }] = a(), M = D?.timeCampusInvoices?.items ?? [], Te = D?.timeCampusInvoices?.total ?? 0, N = be?.timeCampusInvoiceStats, [P, F] = d(!1), [Ee, De] = d(!0), [Oe, I] = d(!1), [L, R] = d(C), [z, B] = d(null), [V, H] = d(null), [ke, U] = d(!1), [W, G] = d(null), [K, q] = d(null), [Ae, J] = d(null), Y = async () => {
|
|
63
|
+
await Promise.all([ve(), Se()]);
|
|
64
|
+
}, je = () => {
|
|
65
|
+
R({
|
|
66
|
+
...C,
|
|
67
|
+
periodStart: S(),
|
|
68
|
+
periodEnd: x()
|
|
69
|
+
}), I(!0);
|
|
70
|
+
}, X = () => I(!1), Me = async () => {
|
|
71
|
+
if (!L.clientName.trim() || !L.periodStart || !L.periodEnd) return;
|
|
72
|
+
let e = Number(L.taxRatePercent || "0") / 100;
|
|
73
|
+
if (Number.isNaN(e) || e < 0 || e > 1) {
|
|
74
|
+
T({
|
|
75
|
+
title: "Tax rate must be between 0 and 100%",
|
|
76
|
+
variant: "error"
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
await Ce({ variables: { input: {
|
|
82
|
+
workspaceId: m,
|
|
83
|
+
clientName: L.clientName.trim(),
|
|
84
|
+
clientEmail: L.clientEmail.trim() || void 0,
|
|
85
|
+
clientAddress: L.clientAddress.trim() || void 0,
|
|
86
|
+
periodStart: new Date(L.periodStart).toISOString(),
|
|
87
|
+
periodEnd: (/* @__PURE__ */ new Date(new Date(L.periodEnd).getTime() + 864e5 - 1)).toISOString(),
|
|
88
|
+
taxRate: e,
|
|
89
|
+
currency: L.currency.trim() || "USD",
|
|
90
|
+
dueDate: L.dueDate ? new Date(L.dueDate).toISOString() : void 0,
|
|
91
|
+
notes: L.notes.trim() || void 0,
|
|
92
|
+
projectIds: L.projectIds.trim() ? L.projectIds.split(",").map((e) => e.trim()).filter(Boolean) : void 0
|
|
93
|
+
} } }), T({
|
|
94
|
+
title: "Draft invoice generated",
|
|
95
|
+
variant: "success"
|
|
96
|
+
}), X(), await Y();
|
|
97
|
+
} catch (e) {
|
|
98
|
+
T({
|
|
99
|
+
title: e instanceof Error ? e.message : "Failed to generate invoice — check the billing period has un-invoiced billable time",
|
|
100
|
+
variant: "error"
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}, Z = async (e) => {
|
|
104
|
+
J(e.id);
|
|
105
|
+
try {
|
|
106
|
+
await A({ variables: { input: {
|
|
107
|
+
id: e.id,
|
|
108
|
+
workspaceId: m,
|
|
109
|
+
status: u.Sent
|
|
110
|
+
} } }), T({
|
|
111
|
+
title: "Invoice marked as sent",
|
|
112
|
+
variant: "success"
|
|
113
|
+
}), await Y();
|
|
114
|
+
} catch {
|
|
115
|
+
T({
|
|
116
|
+
title: "Failed to update invoice status",
|
|
117
|
+
variant: "error"
|
|
118
|
+
});
|
|
119
|
+
} finally {
|
|
120
|
+
J(null);
|
|
121
|
+
}
|
|
122
|
+
}, Ne = async (e) => {
|
|
123
|
+
J(e.id);
|
|
124
|
+
try {
|
|
125
|
+
await A({ variables: { input: {
|
|
126
|
+
id: e.id,
|
|
127
|
+
workspaceId: m,
|
|
128
|
+
status: u.Paid
|
|
129
|
+
} } }), T({
|
|
130
|
+
title: "Invoice marked as paid",
|
|
131
|
+
variant: "success"
|
|
132
|
+
}), await Y();
|
|
133
|
+
} catch {
|
|
134
|
+
T({
|
|
135
|
+
title: "Failed to update invoice status",
|
|
136
|
+
variant: "error"
|
|
137
|
+
});
|
|
138
|
+
} finally {
|
|
139
|
+
J(null);
|
|
140
|
+
}
|
|
141
|
+
}, Pe = async () => {
|
|
142
|
+
if (K) try {
|
|
143
|
+
await we({ variables: {
|
|
144
|
+
id: K.id,
|
|
145
|
+
workspaceId: m
|
|
146
|
+
} }), T({
|
|
147
|
+
title: "Draft invoice deleted",
|
|
148
|
+
variant: "success"
|
|
149
|
+
}), q(null), await Y();
|
|
150
|
+
} catch {
|
|
151
|
+
T({
|
|
152
|
+
title: "Failed to delete invoice",
|
|
153
|
+
variant: "error"
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}, Fe = async (e) => {
|
|
157
|
+
B(e), H(null), U(!0);
|
|
158
|
+
try {
|
|
159
|
+
let { data: t } = await E.query({
|
|
160
|
+
query: pe,
|
|
161
|
+
variables: {
|
|
162
|
+
id: e.id,
|
|
163
|
+
workspaceId: m
|
|
164
|
+
},
|
|
165
|
+
fetchPolicy: "no-cache"
|
|
166
|
+
}), n = t?.timeCampusInvoiceHtml;
|
|
167
|
+
H(n ?? null);
|
|
168
|
+
} catch {
|
|
169
|
+
T({
|
|
170
|
+
title: "Failed to load invoice preview",
|
|
171
|
+
variant: "error"
|
|
172
|
+
});
|
|
173
|
+
} finally {
|
|
174
|
+
U(!1);
|
|
175
|
+
}
|
|
176
|
+
}, Q = async (e) => {
|
|
177
|
+
G(e.id);
|
|
178
|
+
try {
|
|
179
|
+
let { data: t } = await E.query({
|
|
180
|
+
query: me,
|
|
181
|
+
variables: {
|
|
182
|
+
id: e.id,
|
|
183
|
+
workspaceId: m
|
|
184
|
+
},
|
|
185
|
+
fetchPolicy: "no-cache"
|
|
186
|
+
}), n = t?.timeCampusInvoicePdf;
|
|
187
|
+
if (!n) throw Error("Empty PDF payload");
|
|
188
|
+
he(n, `${e.invoiceNumber}.pdf`);
|
|
189
|
+
} catch {
|
|
190
|
+
T({
|
|
191
|
+
title: "Failed to download PDF",
|
|
192
|
+
variant: "error"
|
|
193
|
+
});
|
|
194
|
+
} finally {
|
|
195
|
+
G(null);
|
|
196
|
+
}
|
|
197
|
+
}, Ie = async () => {
|
|
198
|
+
F(!0);
|
|
199
|
+
try {
|
|
200
|
+
((await ye({
|
|
201
|
+
variables: {
|
|
202
|
+
workspaceId: m,
|
|
203
|
+
pagination: {
|
|
204
|
+
limit: y,
|
|
205
|
+
offset: M.length
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
updateQuery: (e, { fetchMoreResult: t }) => t ? {
|
|
209
|
+
...e,
|
|
210
|
+
timeCampusInvoices: {
|
|
211
|
+
...t.timeCampusInvoices,
|
|
212
|
+
items: [...e.timeCampusInvoices.items, ...t.timeCampusInvoices.items]
|
|
213
|
+
}
|
|
214
|
+
} : e
|
|
215
|
+
})).data?.timeCampusInvoices?.items?.length ?? 0) < y && De(!1);
|
|
216
|
+
} finally {
|
|
217
|
+
F(!1);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
if (O && xe && M.length === 0) return /* @__PURE__ */ _(c, {
|
|
221
|
+
title: "Invoicing",
|
|
222
|
+
description: "Bill clients for billable time",
|
|
223
|
+
children: /* @__PURE__ */ _(i, {
|
|
224
|
+
variant: "list",
|
|
225
|
+
count: 4
|
|
226
|
+
})
|
|
227
|
+
});
|
|
228
|
+
if (_e) return /* @__PURE__ */ _(c, {
|
|
229
|
+
title: "Invoicing",
|
|
230
|
+
description: "Bill clients for billable time",
|
|
231
|
+
children: /* @__PURE__ */ _("div", {
|
|
232
|
+
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
233
|
+
children: "Failed to load invoices. Please try again later."
|
|
234
|
+
})
|
|
235
|
+
});
|
|
236
|
+
let $ = /* @__PURE__ */ v("button", {
|
|
237
|
+
onClick: je,
|
|
238
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
239
|
+
children: [/* @__PURE__ */ _(ue, { className: "h-4 w-4" }), "Generate Invoice"]
|
|
240
|
+
});
|
|
241
|
+
return /* @__PURE__ */ v(c, {
|
|
242
|
+
title: "Invoicing",
|
|
243
|
+
description: "Bill clients for billable time",
|
|
244
|
+
purpose: "Turn logged, billable hours into a real invoice — pick a client and a billing period, and TimeCampus aggregates every un-invoiced billable time entry by project and task, applying each entry's own rate, into a draft you can review, send, and mark paid.",
|
|
245
|
+
actions: $,
|
|
246
|
+
children: [
|
|
247
|
+
/* @__PURE__ */ v("div", {
|
|
248
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
249
|
+
children: [
|
|
250
|
+
/* @__PURE__ */ _(r, {
|
|
251
|
+
featured: !0,
|
|
252
|
+
title: "Total Invoiced",
|
|
253
|
+
value: t(N?.totalInvoiced ?? 0),
|
|
254
|
+
icon: /* @__PURE__ */ _(ce, { className: "h-5 w-5" })
|
|
255
|
+
}),
|
|
256
|
+
/* @__PURE__ */ _(r, {
|
|
257
|
+
title: "Outstanding",
|
|
258
|
+
value: t(N?.totalOutstanding ?? 0),
|
|
259
|
+
description: `${N?.sentCount ?? 0} sent, awaiting payment`
|
|
260
|
+
}),
|
|
261
|
+
/* @__PURE__ */ _(r, {
|
|
262
|
+
title: "Paid",
|
|
263
|
+
value: t(N?.totalPaid ?? 0),
|
|
264
|
+
description: `${N?.paidCount ?? 0} invoices paid`
|
|
265
|
+
}),
|
|
266
|
+
/* @__PURE__ */ _(r, {
|
|
267
|
+
title: "Drafts",
|
|
268
|
+
value: String(N?.draftCount ?? 0),
|
|
269
|
+
icon: /* @__PURE__ */ _(p, { className: "h-5 w-5" }),
|
|
270
|
+
description: "not yet sent"
|
|
271
|
+
})
|
|
272
|
+
]
|
|
273
|
+
}),
|
|
274
|
+
M.length === 0 && !O ? /* @__PURE__ */ _(re, {
|
|
275
|
+
illustration: "empty-data",
|
|
276
|
+
title: "No invoices yet",
|
|
277
|
+
description: "Generate a draft invoice from real billable time entries to bill a client.",
|
|
278
|
+
action: $
|
|
279
|
+
}) : /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _("div", {
|
|
280
|
+
className: "mt-4 rounded-xl border border-border-seam bg-gloss-card shadow-pop",
|
|
281
|
+
children: /* @__PURE__ */ _("div", {
|
|
282
|
+
className: "divide-y divide-border",
|
|
283
|
+
children: M.map((e) => {
|
|
284
|
+
let r = Ae === e.id, i = e.status === u.Draft, a = e.status === u.Sent;
|
|
285
|
+
return /* @__PURE__ */ v("div", {
|
|
286
|
+
className: "flex flex-col gap-3 p-4 hover:bg-bg-sunken/50 transition-colors sm:flex-row sm:items-center sm:justify-between",
|
|
287
|
+
children: [/* @__PURE__ */ v("div", {
|
|
288
|
+
className: "flex items-center gap-3",
|
|
289
|
+
children: [/* @__PURE__ */ _(p, { className: "h-5 w-5 shrink-0 text-text-secondary" }), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ v("p", {
|
|
290
|
+
className: "text-sm font-medium text-text-primary",
|
|
291
|
+
children: [
|
|
292
|
+
e.invoiceNumber,
|
|
293
|
+
" · ",
|
|
294
|
+
e.clientName
|
|
295
|
+
]
|
|
296
|
+
}), /* @__PURE__ */ v("p", {
|
|
297
|
+
className: "text-xs text-text-secondary",
|
|
298
|
+
children: [
|
|
299
|
+
n(e.periodStart),
|
|
300
|
+
" –",
|
|
301
|
+
" ",
|
|
302
|
+
n(e.periodEnd),
|
|
303
|
+
e.dueDate && /* @__PURE__ */ v(g, { children: [" · Due ", n(e.dueDate)] })
|
|
304
|
+
]
|
|
305
|
+
})] })]
|
|
306
|
+
}), /* @__PURE__ */ v("div", {
|
|
307
|
+
className: "flex items-center gap-3",
|
|
308
|
+
children: [
|
|
309
|
+
/* @__PURE__ */ _("p", {
|
|
310
|
+
className: "text-sm font-medium text-text-primary",
|
|
311
|
+
children: t(e.total, e.currency)
|
|
312
|
+
}),
|
|
313
|
+
/* @__PURE__ */ _(ne, {
|
|
314
|
+
label: e.status.toLowerCase(),
|
|
315
|
+
status: e.status.toLowerCase()
|
|
316
|
+
}),
|
|
317
|
+
e.isOverdue && /* @__PURE__ */ v("span", {
|
|
318
|
+
title: "Past due date",
|
|
319
|
+
className: "inline-flex items-center gap-1 text-xs font-medium text-status-error-text",
|
|
320
|
+
children: [/* @__PURE__ */ _(oe, { className: "h-3.5 w-3.5" }), "Overdue"]
|
|
321
|
+
}),
|
|
322
|
+
/* @__PURE__ */ v("div", {
|
|
323
|
+
className: "flex items-center gap-1",
|
|
324
|
+
children: [
|
|
325
|
+
/* @__PURE__ */ _("button", {
|
|
326
|
+
type: "button",
|
|
327
|
+
onClick: () => void Fe(e),
|
|
328
|
+
title: "View invoice",
|
|
329
|
+
className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken",
|
|
330
|
+
children: /* @__PURE__ */ _(le, { className: "h-4 w-4" })
|
|
331
|
+
}),
|
|
332
|
+
/* @__PURE__ */ _("button", {
|
|
333
|
+
type: "button",
|
|
334
|
+
disabled: W === e.id,
|
|
335
|
+
onClick: () => void Q(e),
|
|
336
|
+
title: "Download PDF",
|
|
337
|
+
className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken disabled:opacity-50",
|
|
338
|
+
children: /* @__PURE__ */ _(f, { className: "h-4 w-4" })
|
|
339
|
+
}),
|
|
340
|
+
i && /* @__PURE__ */ _("button", {
|
|
341
|
+
type: "button",
|
|
342
|
+
disabled: r,
|
|
343
|
+
onClick: () => Z(e),
|
|
344
|
+
title: "Mark as sent",
|
|
345
|
+
className: "rounded-md p-1.5 text-status-info-text hover:bg-status-info-bg disabled:opacity-50",
|
|
346
|
+
children: /* @__PURE__ */ _(de, { className: "h-4 w-4" })
|
|
347
|
+
}),
|
|
348
|
+
a && /* @__PURE__ */ _("button", {
|
|
349
|
+
type: "button",
|
|
350
|
+
disabled: r,
|
|
351
|
+
onClick: () => Ne(e),
|
|
352
|
+
title: "Mark as paid",
|
|
353
|
+
className: "rounded-md p-1.5 text-status-success-text hover:bg-status-success-bg disabled:opacity-50",
|
|
354
|
+
children: /* @__PURE__ */ _(se, { className: "h-4 w-4" })
|
|
355
|
+
}),
|
|
356
|
+
i && /* @__PURE__ */ _("button", {
|
|
357
|
+
type: "button",
|
|
358
|
+
onClick: () => q(e),
|
|
359
|
+
title: "Delete draft",
|
|
360
|
+
className: "rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg",
|
|
361
|
+
children: /* @__PURE__ */ _(fe, { className: "h-4 w-4" })
|
|
362
|
+
})
|
|
363
|
+
]
|
|
364
|
+
})
|
|
365
|
+
]
|
|
366
|
+
})]
|
|
367
|
+
}, e.id);
|
|
368
|
+
})
|
|
369
|
+
})
|
|
370
|
+
}), Ee && M.length < Te && /* @__PURE__ */ _("div", {
|
|
371
|
+
className: "mt-4 flex justify-center",
|
|
372
|
+
children: /* @__PURE__ */ _("button", {
|
|
373
|
+
onClick: Ie,
|
|
374
|
+
disabled: P,
|
|
375
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50",
|
|
376
|
+
children: P ? "Loading..." : "Load more"
|
|
377
|
+
})
|
|
378
|
+
})] }),
|
|
379
|
+
/* @__PURE__ */ _(l, {
|
|
380
|
+
open: Oe,
|
|
381
|
+
onClose: X,
|
|
382
|
+
title: "Generate Invoice",
|
|
383
|
+
description: "Aggregates every billable, un-invoiced time entry in the selected period into a draft invoice.",
|
|
384
|
+
footer: /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _("button", {
|
|
385
|
+
onClick: X,
|
|
386
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
387
|
+
children: "Cancel"
|
|
388
|
+
}), /* @__PURE__ */ _("button", {
|
|
389
|
+
onClick: Me,
|
|
390
|
+
disabled: k || !L.clientName.trim() || !L.periodStart || !L.periodEnd,
|
|
391
|
+
className: "rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50",
|
|
392
|
+
children: k ? "Generating..." : "Generate Draft"
|
|
393
|
+
})] }),
|
|
394
|
+
children: /* @__PURE__ */ v("div", {
|
|
395
|
+
className: "space-y-4",
|
|
396
|
+
children: [
|
|
397
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
398
|
+
className: "block text-sm font-medium text-text-primary",
|
|
399
|
+
children: "Client name"
|
|
400
|
+
}), /* @__PURE__ */ _("input", {
|
|
401
|
+
type: "text",
|
|
402
|
+
placeholder: "Acme Corp",
|
|
403
|
+
value: L.clientName,
|
|
404
|
+
onChange: (e) => R({
|
|
405
|
+
...L,
|
|
406
|
+
clientName: e.target.value
|
|
407
|
+
}),
|
|
408
|
+
className: b
|
|
409
|
+
})] }),
|
|
410
|
+
/* @__PURE__ */ v("div", {
|
|
411
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
412
|
+
children: [/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
413
|
+
className: "block text-sm font-medium text-text-primary",
|
|
414
|
+
children: "Client email"
|
|
415
|
+
}), /* @__PURE__ */ _("input", {
|
|
416
|
+
type: "email",
|
|
417
|
+
placeholder: "billing@acme.com",
|
|
418
|
+
value: L.clientEmail,
|
|
419
|
+
onChange: (e) => R({
|
|
420
|
+
...L,
|
|
421
|
+
clientEmail: e.target.value
|
|
422
|
+
}),
|
|
423
|
+
className: b
|
|
424
|
+
})] }), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
425
|
+
className: "block text-sm font-medium text-text-primary",
|
|
426
|
+
children: "Currency"
|
|
427
|
+
}), /* @__PURE__ */ _("input", {
|
|
428
|
+
type: "text",
|
|
429
|
+
placeholder: "USD",
|
|
430
|
+
value: L.currency,
|
|
431
|
+
onChange: (e) => R({
|
|
432
|
+
...L,
|
|
433
|
+
currency: e.target.value
|
|
434
|
+
}),
|
|
435
|
+
className: b
|
|
436
|
+
})] })]
|
|
437
|
+
}),
|
|
438
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
439
|
+
className: "block text-sm font-medium text-text-primary",
|
|
440
|
+
children: "Client address"
|
|
441
|
+
}), /* @__PURE__ */ _("input", {
|
|
442
|
+
type: "text",
|
|
443
|
+
placeholder: "Optional billing address",
|
|
444
|
+
value: L.clientAddress,
|
|
445
|
+
onChange: (e) => R({
|
|
446
|
+
...L,
|
|
447
|
+
clientAddress: e.target.value
|
|
448
|
+
}),
|
|
449
|
+
className: b
|
|
450
|
+
})] }),
|
|
451
|
+
/* @__PURE__ */ v("div", {
|
|
452
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
453
|
+
children: [/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
454
|
+
className: "block text-sm font-medium text-text-primary",
|
|
455
|
+
children: "Billing period start"
|
|
456
|
+
}), /* @__PURE__ */ _("input", {
|
|
457
|
+
type: "date",
|
|
458
|
+
value: L.periodStart,
|
|
459
|
+
onChange: (e) => R({
|
|
460
|
+
...L,
|
|
461
|
+
periodStart: e.target.value
|
|
462
|
+
}),
|
|
463
|
+
className: b
|
|
464
|
+
})] }), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
465
|
+
className: "block text-sm font-medium text-text-primary",
|
|
466
|
+
children: "Billing period end"
|
|
467
|
+
}), /* @__PURE__ */ _("input", {
|
|
468
|
+
type: "date",
|
|
469
|
+
value: L.periodEnd,
|
|
470
|
+
onChange: (e) => R({
|
|
471
|
+
...L,
|
|
472
|
+
periodEnd: e.target.value
|
|
473
|
+
}),
|
|
474
|
+
className: b
|
|
475
|
+
})] })]
|
|
476
|
+
}),
|
|
477
|
+
/* @__PURE__ */ v("div", {
|
|
478
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
479
|
+
children: [/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
480
|
+
className: "block text-sm font-medium text-text-primary",
|
|
481
|
+
children: "Tax rate (%)"
|
|
482
|
+
}), /* @__PURE__ */ _("input", {
|
|
483
|
+
type: "number",
|
|
484
|
+
min: 0,
|
|
485
|
+
max: 100,
|
|
486
|
+
step: "0.1",
|
|
487
|
+
value: L.taxRatePercent,
|
|
488
|
+
onChange: (e) => R({
|
|
489
|
+
...L,
|
|
490
|
+
taxRatePercent: e.target.value
|
|
491
|
+
}),
|
|
492
|
+
className: b
|
|
493
|
+
})] }), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
494
|
+
className: "block text-sm font-medium text-text-primary",
|
|
495
|
+
children: "Due date"
|
|
496
|
+
}), /* @__PURE__ */ _("input", {
|
|
497
|
+
type: "date",
|
|
498
|
+
value: L.dueDate,
|
|
499
|
+
onChange: (e) => R({
|
|
500
|
+
...L,
|
|
501
|
+
dueDate: e.target.value
|
|
502
|
+
}),
|
|
503
|
+
className: b
|
|
504
|
+
})] })]
|
|
505
|
+
}),
|
|
506
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
507
|
+
className: "block text-sm font-medium text-text-primary",
|
|
508
|
+
children: "Project IDs"
|
|
509
|
+
}), /* @__PURE__ */ _("input", {
|
|
510
|
+
type: "text",
|
|
511
|
+
placeholder: "Optional, comma-separated. Leave blank for all projects.",
|
|
512
|
+
value: L.projectIds,
|
|
513
|
+
onChange: (e) => R({
|
|
514
|
+
...L,
|
|
515
|
+
projectIds: e.target.value
|
|
516
|
+
}),
|
|
517
|
+
className: b
|
|
518
|
+
})] }),
|
|
519
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
520
|
+
className: "block text-sm font-medium text-text-primary",
|
|
521
|
+
children: "Notes"
|
|
522
|
+
}), /* @__PURE__ */ _("textarea", {
|
|
523
|
+
rows: 2,
|
|
524
|
+
placeholder: "Optional notes printed on the invoice",
|
|
525
|
+
value: L.notes,
|
|
526
|
+
onChange: (e) => R({
|
|
527
|
+
...L,
|
|
528
|
+
notes: e.target.value
|
|
529
|
+
}),
|
|
530
|
+
className: b
|
|
531
|
+
})] })
|
|
532
|
+
]
|
|
533
|
+
})
|
|
534
|
+
}),
|
|
535
|
+
/* @__PURE__ */ _(l, {
|
|
536
|
+
open: z !== null,
|
|
537
|
+
onClose: () => B(null),
|
|
538
|
+
title: z ? `Invoice ${z.invoiceNumber}` : "Invoice",
|
|
539
|
+
description: z?.clientName,
|
|
540
|
+
size: "xl",
|
|
541
|
+
footer: z && /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _("button", {
|
|
542
|
+
onClick: () => B(null),
|
|
543
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
544
|
+
children: "Close"
|
|
545
|
+
}), /* @__PURE__ */ v("button", {
|
|
546
|
+
onClick: () => void Q(z),
|
|
547
|
+
disabled: W === z.id,
|
|
548
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50",
|
|
549
|
+
children: [/* @__PURE__ */ _(f, { className: "h-4 w-4" }), W === z.id ? "Downloading..." : "Download PDF"]
|
|
550
|
+
})] }),
|
|
551
|
+
children: ke ? /* @__PURE__ */ _(i, {
|
|
552
|
+
variant: "detail",
|
|
553
|
+
count: 1
|
|
554
|
+
}) : V ? /* @__PURE__ */ _("iframe", {
|
|
555
|
+
title: "Invoice preview",
|
|
556
|
+
srcDoc: V,
|
|
557
|
+
sandbox: "",
|
|
558
|
+
className: "h-[60vh] w-full rounded-md border border-border-subtle bg-white"
|
|
559
|
+
}) : /* @__PURE__ */ _("p", {
|
|
560
|
+
className: "text-sm text-text-secondary",
|
|
561
|
+
children: "Failed to load invoice preview."
|
|
562
|
+
})
|
|
563
|
+
}),
|
|
564
|
+
/* @__PURE__ */ _(l, {
|
|
565
|
+
open: K !== null,
|
|
566
|
+
onClose: () => q(null),
|
|
567
|
+
title: "Delete draft invoice?",
|
|
568
|
+
description: K ? `${K.invoiceNumber} · ${t(K.total, K.currency)} will be removed and its time entries released back to the un-invoiced pool.` : void 0,
|
|
569
|
+
size: "sm",
|
|
570
|
+
footer: /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _("button", {
|
|
571
|
+
onClick: () => q(null),
|
|
572
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
573
|
+
children: "Cancel"
|
|
574
|
+
}), /* @__PURE__ */ _("button", {
|
|
575
|
+
onClick: Pe,
|
|
576
|
+
disabled: j,
|
|
577
|
+
className: "rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50",
|
|
578
|
+
children: j ? "Deleting..." : "Delete"
|
|
579
|
+
})] }),
|
|
580
|
+
children: /* @__PURE__ */ _("p", {
|
|
581
|
+
className: "text-sm text-text-secondary",
|
|
582
|
+
children: "This permanently removes the draft. This action cannot be undone."
|
|
583
|
+
})
|
|
584
|
+
}),
|
|
585
|
+
/* @__PURE__ */ _(ie, {
|
|
586
|
+
toasts: w,
|
|
587
|
+
onDismiss: ge
|
|
588
|
+
})
|
|
589
|
+
]
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
//#endregion
|
|
593
|
+
export { w as InvoicingPage };
|
|
594
|
+
|
|
595
|
+
//# sourceMappingURL=InvoicingPage.js.map
|