@burdenoff/microfe-timecampus 2026.703.2 → 2026.703.4
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/timecampus/pages/client-portal/ClientPortalPage.js +236 -159
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +275 -161
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingFormModal.js +138 -75
- package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +120 -96
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
|
|
2
|
-
import { formatCurrency as t, formatDate as
|
|
3
|
-
import { StatCard as
|
|
4
|
-
import { LoadingSkeleton as
|
|
5
|
-
import { useApproveExpenseMutation as
|
|
6
|
-
import { PageLayout as
|
|
7
|
-
import { EmptyState as
|
|
8
|
-
import { ExpenseStatus as
|
|
9
|
-
import { Modal as
|
|
10
|
-
import { StatusBadge as
|
|
11
|
-
import { ToastContainer as
|
|
12
|
-
import { useState as
|
|
13
|
-
import { Check as
|
|
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 { useApproveExpenseMutation as a, useCreateExpenseMutation as ee, useDeleteExpenseMutation as te, useGetExpenseStatsQuery as ne, useListExpensesQuery as re, useRejectExpenseMutation as ie, useSubmitExpenseForApprovalMutation as ae, useUpdateExpenseMutation as oe } from "../../../generated/wspace-operations.js";
|
|
6
|
+
import { PageLayout as o } from "../../components/PageLayout.js";
|
|
7
|
+
import { EmptyState as se } from "../../components/EmptyState.js";
|
|
8
|
+
import { ExpenseStatus as s } from "../../../generated/wspace-types.js";
|
|
9
|
+
import { Modal as c } from "../../components/Modal.js";
|
|
10
|
+
import { StatusBadge as ce } from "../../components/StatusBadge.js";
|
|
11
|
+
import { ToastContainer as le, useToast as ue } from "../../components/Toast.js";
|
|
12
|
+
import { useState as l } from "react";
|
|
13
|
+
import { Check as de, DollarSign as fe, ExternalLink as pe, MessageSquare as me, Pencil as he, Plus as ge, Receipt as u, Send as _e, Trash2 as ve, Upload as ye, X as be } from "lucide-react";
|
|
14
|
+
import { gql as d } from "@apollo/client";
|
|
15
|
+
import { useApolloClient as xe } from "@apollo/client/react";
|
|
14
16
|
import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
17
|
+
import { uploadBlobToFiles as Se } from "@burdenoff/fe-libs/storage";
|
|
18
|
+
import { ContextConversationWidget as h } from "@burdenoff/fe-libs/shared";
|
|
19
|
+
import { useRegisterPageConversationContext as g } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
15
20
|
//#region src/timecampus/pages/expense-tracking/ExpenseTrackingPage.tsx
|
|
16
|
-
var
|
|
21
|
+
var _ = 25, v = [
|
|
17
22
|
"Travel",
|
|
18
23
|
"Meals",
|
|
19
24
|
"Software",
|
|
@@ -21,171 +26,230 @@ var h = 25, g = [
|
|
|
21
26
|
"Office",
|
|
22
27
|
"Training",
|
|
23
28
|
"Other"
|
|
24
|
-
],
|
|
25
|
-
|
|
29
|
+
], y = "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", Ce = d`
|
|
30
|
+
query TimeCampusExpense_GetFileDownloadUrl($id: ID!, $expiresIn: Int) {
|
|
31
|
+
getFileDownloadUrl(id: $id, expiresIn: $expiresIn)
|
|
32
|
+
}
|
|
33
|
+
`, we = d`
|
|
34
|
+
mutation TimeCampusExpense_EnsureFolder($path: String!) {
|
|
35
|
+
ensureFolderByPath(path: $path) {
|
|
36
|
+
id
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
`, Te = 604800;
|
|
40
|
+
function Ee({ expense: e }) {
|
|
41
|
+
return g({
|
|
42
|
+
entityType: "TIMECAMPUS_EXPENSE",
|
|
43
|
+
entityId: e.id,
|
|
44
|
+
contextTitle: `Expense · ${e.description ?? e.id}`
|
|
45
|
+
}), /* @__PURE__ */ p(h, { widgetKey: "expense-review" });
|
|
46
|
+
}
|
|
47
|
+
function b() {
|
|
26
48
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
27
49
|
}
|
|
28
|
-
var
|
|
50
|
+
var x = {
|
|
29
51
|
amount: "",
|
|
30
|
-
category:
|
|
52
|
+
category: v[0],
|
|
31
53
|
description: "",
|
|
32
54
|
projectId: "",
|
|
33
|
-
date:
|
|
55
|
+
date: b(),
|
|
56
|
+
receiptUrl: ""
|
|
34
57
|
};
|
|
35
|
-
function
|
|
36
|
-
let { workspaceId:
|
|
58
|
+
function S() {
|
|
59
|
+
let { workspaceId: d = "" } = e(), { toasts: h, addToast: g, dismissToast: S } = ue(), C = xe(), { data: De, loading: w, error: Oe, refetch: T, fetchMore: ke } = re({
|
|
37
60
|
variables: {
|
|
38
|
-
workspaceId:
|
|
39
|
-
limit:
|
|
61
|
+
workspaceId: d,
|
|
62
|
+
limit: _,
|
|
40
63
|
offset: 0
|
|
41
64
|
},
|
|
42
65
|
notifyOnNetworkStatusChange: !0
|
|
43
|
-
}), { data:
|
|
44
|
-
await Promise.all([
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
...
|
|
48
|
-
date:
|
|
49
|
-
}),
|
|
50
|
-
},
|
|
51
|
-
|
|
66
|
+
}), { data: Ae, loading: je, error: Me, refetch: Ne } = ne({ variables: { workspaceId: d } }), [Pe, { loading: E }] = ee(), [Fe, { loading: D }] = oe(), [Ie, { loading: O }] = te(), [Le] = ae(), [Re] = a(), [ze, { loading: k }] = ie(), A = De?.expenses ?? [], j = Ae?.expenseStats, [M, N] = l(!1), [Be, Ve] = l(!0), [He, P] = l(!1), [F, I] = l(null), [L, R] = l(x), [z, B] = l(!1), [V, H] = l(null), [U, W] = l(null), [G, K] = l(null), [q, J] = l(""), [Ue, Y] = l(null), X = async () => {
|
|
67
|
+
await Promise.all([T(), Ne()]);
|
|
68
|
+
}, We = () => {
|
|
69
|
+
I(null), R({
|
|
70
|
+
...x,
|
|
71
|
+
date: b()
|
|
72
|
+
}), P(!0);
|
|
73
|
+
}, Ge = (e) => {
|
|
74
|
+
I(e), R({
|
|
52
75
|
amount: String(e.amount),
|
|
53
76
|
category: e.category,
|
|
54
77
|
description: e.description ?? "",
|
|
55
78
|
projectId: e.projectId ?? "",
|
|
56
|
-
date: e.date ? e.date.slice(0, 10) :
|
|
57
|
-
|
|
79
|
+
date: e.date ? e.date.slice(0, 10) : b(),
|
|
80
|
+
receiptUrl: e.receiptUrl ?? ""
|
|
81
|
+
}), P(!0);
|
|
82
|
+
}, Ke = async (e) => {
|
|
83
|
+
B(!0);
|
|
84
|
+
try {
|
|
85
|
+
let { data: t } = await C.mutate({
|
|
86
|
+
mutation: we,
|
|
87
|
+
variables: { path: "/TimeCampus/Receipts" }
|
|
88
|
+
}), n = t?.ensureFolderByPath?.id;
|
|
89
|
+
if (!n) throw Error("Could not resolve upload folder");
|
|
90
|
+
let r = await Se({
|
|
91
|
+
client: C,
|
|
92
|
+
blob: e,
|
|
93
|
+
filename: e.name,
|
|
94
|
+
mimeType: e.type || "application/octet-stream",
|
|
95
|
+
folderId: n
|
|
96
|
+
}), { data: i } = await C.query({
|
|
97
|
+
query: Ce,
|
|
98
|
+
variables: {
|
|
99
|
+
id: r.id,
|
|
100
|
+
expiresIn: Te
|
|
101
|
+
},
|
|
102
|
+
fetchPolicy: "no-cache"
|
|
103
|
+
}), a = i?.getFileDownloadUrl;
|
|
104
|
+
if (!a) throw Error("No download URL returned");
|
|
105
|
+
R((e) => ({
|
|
106
|
+
...e,
|
|
107
|
+
receiptUrl: a
|
|
108
|
+
})), g({
|
|
109
|
+
title: "Receipt attached",
|
|
110
|
+
variant: "success"
|
|
111
|
+
});
|
|
112
|
+
} catch {
|
|
113
|
+
g({
|
|
114
|
+
title: "Failed to upload receipt",
|
|
115
|
+
variant: "error"
|
|
116
|
+
});
|
|
117
|
+
} finally {
|
|
118
|
+
B(!1);
|
|
119
|
+
}
|
|
58
120
|
}, Z = () => {
|
|
59
|
-
|
|
121
|
+
P(!1), I(null);
|
|
60
122
|
}, Q = async () => {
|
|
61
|
-
let e = Number(
|
|
62
|
-
if (!
|
|
63
|
-
let t = new Date(
|
|
123
|
+
let e = Number(L.amount);
|
|
124
|
+
if (!L.amount || Number.isNaN(e) || !L.category.trim()) return;
|
|
125
|
+
let t = new Date(L.date).toISOString();
|
|
64
126
|
try {
|
|
65
|
-
|
|
66
|
-
id:
|
|
67
|
-
workspaceId:
|
|
127
|
+
F ? (await Fe({ variables: {
|
|
128
|
+
id: F.id,
|
|
129
|
+
workspaceId: d,
|
|
68
130
|
input: {
|
|
69
131
|
amount: e,
|
|
70
|
-
category:
|
|
71
|
-
description:
|
|
72
|
-
projectId:
|
|
73
|
-
date: t
|
|
132
|
+
category: L.category.trim(),
|
|
133
|
+
description: L.description.trim() || void 0,
|
|
134
|
+
projectId: L.projectId.trim() || void 0,
|
|
135
|
+
date: t,
|
|
136
|
+
receiptUrl: L.receiptUrl || void 0
|
|
74
137
|
}
|
|
75
|
-
} }),
|
|
138
|
+
} }), g({
|
|
76
139
|
title: "Expense updated",
|
|
77
140
|
variant: "success"
|
|
78
|
-
})) : (await
|
|
79
|
-
workspaceId:
|
|
141
|
+
})) : (await Pe({ variables: { input: {
|
|
142
|
+
workspaceId: d,
|
|
80
143
|
amount: e,
|
|
81
|
-
category:
|
|
82
|
-
description:
|
|
83
|
-
projectId:
|
|
84
|
-
date: t
|
|
85
|
-
|
|
144
|
+
category: L.category.trim(),
|
|
145
|
+
description: L.description.trim() || void 0,
|
|
146
|
+
projectId: L.projectId.trim() || void 0,
|
|
147
|
+
date: t,
|
|
148
|
+
receiptUrl: L.receiptUrl || void 0
|
|
149
|
+
} } }), g({
|
|
86
150
|
title: "Expense added",
|
|
87
151
|
variant: "success"
|
|
88
152
|
})), Z(), await X();
|
|
89
153
|
} catch {
|
|
90
|
-
|
|
91
|
-
title:
|
|
154
|
+
g({
|
|
155
|
+
title: F ? "Failed to update expense" : "Failed to add expense",
|
|
92
156
|
variant: "error"
|
|
93
157
|
});
|
|
94
158
|
}
|
|
95
|
-
},
|
|
159
|
+
}, qe = async () => {
|
|
96
160
|
if (U) try {
|
|
97
|
-
await
|
|
161
|
+
await Ie({ variables: {
|
|
98
162
|
id: U.id,
|
|
99
|
-
workspaceId:
|
|
100
|
-
} }),
|
|
163
|
+
workspaceId: d
|
|
164
|
+
} }), g({
|
|
101
165
|
title: "Expense deleted",
|
|
102
166
|
variant: "success"
|
|
103
167
|
}), W(null), await X();
|
|
104
168
|
} catch {
|
|
105
|
-
|
|
169
|
+
g({
|
|
106
170
|
title: "Failed to delete expense",
|
|
107
171
|
variant: "error"
|
|
108
172
|
});
|
|
109
173
|
}
|
|
110
|
-
},
|
|
174
|
+
}, Je = async (e) => {
|
|
111
175
|
Y(e.id);
|
|
112
176
|
try {
|
|
113
|
-
await
|
|
177
|
+
await Le({ variables: {
|
|
114
178
|
id: e.id,
|
|
115
|
-
workspaceId:
|
|
116
|
-
} }),
|
|
179
|
+
workspaceId: d
|
|
180
|
+
} }), g({
|
|
117
181
|
title: "Submitted for approval",
|
|
118
182
|
variant: "success"
|
|
119
183
|
}), await X();
|
|
120
184
|
} catch {
|
|
121
|
-
|
|
185
|
+
g({
|
|
122
186
|
title: "Failed to submit expense",
|
|
123
187
|
variant: "error"
|
|
124
188
|
});
|
|
125
189
|
} finally {
|
|
126
190
|
Y(null);
|
|
127
191
|
}
|
|
128
|
-
},
|
|
192
|
+
}, Ye = async (e) => {
|
|
129
193
|
Y(e.id);
|
|
130
194
|
try {
|
|
131
|
-
await
|
|
195
|
+
await Re({ variables: {
|
|
132
196
|
id: e.id,
|
|
133
|
-
workspaceId:
|
|
134
|
-
} }),
|
|
197
|
+
workspaceId: d
|
|
198
|
+
} }), g({
|
|
135
199
|
title: "Expense approved",
|
|
136
200
|
variant: "success"
|
|
137
201
|
}), await X();
|
|
138
202
|
} catch {
|
|
139
|
-
|
|
203
|
+
g({
|
|
140
204
|
title: "Failed to approve expense",
|
|
141
205
|
variant: "error"
|
|
142
206
|
});
|
|
143
207
|
} finally {
|
|
144
208
|
Y(null);
|
|
145
209
|
}
|
|
146
|
-
},
|
|
210
|
+
}, Xe = async () => {
|
|
147
211
|
if (G) try {
|
|
148
|
-
await
|
|
212
|
+
await ze({ variables: {
|
|
149
213
|
id: G.id,
|
|
150
|
-
workspaceId:
|
|
214
|
+
workspaceId: d,
|
|
151
215
|
reason: q.trim() || void 0
|
|
152
|
-
} }),
|
|
216
|
+
} }), g({
|
|
153
217
|
title: "Expense rejected",
|
|
154
218
|
variant: "success"
|
|
155
219
|
}), K(null), J(""), await X();
|
|
156
220
|
} catch {
|
|
157
|
-
|
|
221
|
+
g({
|
|
158
222
|
title: "Failed to reject expense",
|
|
159
223
|
variant: "error"
|
|
160
224
|
});
|
|
161
225
|
}
|
|
162
|
-
},
|
|
163
|
-
|
|
226
|
+
}, Ze = async () => {
|
|
227
|
+
N(!0);
|
|
164
228
|
try {
|
|
165
|
-
((await
|
|
229
|
+
((await ke({
|
|
166
230
|
variables: {
|
|
167
|
-
workspaceId:
|
|
168
|
-
limit:
|
|
169
|
-
offset:
|
|
231
|
+
workspaceId: d,
|
|
232
|
+
limit: _,
|
|
233
|
+
offset: A.length
|
|
170
234
|
},
|
|
171
235
|
updateQuery: (e, { fetchMoreResult: t }) => t ? {
|
|
172
236
|
...e,
|
|
173
237
|
expenses: [...e.expenses, ...t.expenses]
|
|
174
238
|
} : e
|
|
175
|
-
})).data?.expenses?.length ?? 0) <
|
|
239
|
+
})).data?.expenses?.length ?? 0) < _ && Ve(!1);
|
|
176
240
|
} finally {
|
|
177
|
-
|
|
241
|
+
N(!1);
|
|
178
242
|
}
|
|
179
243
|
};
|
|
180
|
-
if (
|
|
244
|
+
if (w && je && A.length === 0) return /* @__PURE__ */ p(o, {
|
|
181
245
|
title: "Expense Tracking",
|
|
182
246
|
description: "Track and manage work expenses",
|
|
183
|
-
children: /* @__PURE__ */ p(
|
|
247
|
+
children: /* @__PURE__ */ p(i, {
|
|
184
248
|
variant: "list",
|
|
185
249
|
count: 4
|
|
186
250
|
})
|
|
187
251
|
});
|
|
188
|
-
if (
|
|
252
|
+
if (Oe && Me) return /* @__PURE__ */ p(o, {
|
|
189
253
|
title: "Expense Tracking",
|
|
190
254
|
description: "Track and manage work expenses",
|
|
191
255
|
children: /* @__PURE__ */ p("div", {
|
|
@@ -194,11 +258,11 @@ function b() {
|
|
|
194
258
|
})
|
|
195
259
|
});
|
|
196
260
|
let $ = /* @__PURE__ */ m("button", {
|
|
197
|
-
onClick:
|
|
261
|
+
onClick: We,
|
|
198
262
|
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",
|
|
199
|
-
children: [/* @__PURE__ */ p(
|
|
263
|
+
children: [/* @__PURE__ */ p(ge, { className: "h-4 w-4" }), "Add Expense"]
|
|
200
264
|
});
|
|
201
|
-
return /* @__PURE__ */ m(
|
|
265
|
+
return /* @__PURE__ */ m(o, {
|
|
202
266
|
title: "Expense Tracking",
|
|
203
267
|
description: "Track and manage work expenses",
|
|
204
268
|
purpose: "Capture project-related costs alongside the hours you log — receipts, categories and approvals in one place — so the bill you send a client reflects the full cost of the work, not just time.",
|
|
@@ -207,31 +271,31 @@ function b() {
|
|
|
207
271
|
/* @__PURE__ */ m("div", {
|
|
208
272
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
209
273
|
children: [
|
|
210
|
-
/* @__PURE__ */ p(
|
|
274
|
+
/* @__PURE__ */ p(r, {
|
|
211
275
|
featured: !0,
|
|
212
276
|
title: "Total This Month",
|
|
213
|
-
value: t(
|
|
214
|
-
icon: /* @__PURE__ */ p(
|
|
277
|
+
value: t(j?.totalAmount ?? 0),
|
|
278
|
+
icon: /* @__PURE__ */ p(fe, { className: "h-5 w-5" })
|
|
215
279
|
}),
|
|
216
|
-
/* @__PURE__ */ p(
|
|
280
|
+
/* @__PURE__ */ p(r, {
|
|
217
281
|
title: "Pending",
|
|
218
|
-
value: String(
|
|
282
|
+
value: String(j?.pendingCount ?? 0),
|
|
219
283
|
description: "expenses awaiting approval"
|
|
220
284
|
}),
|
|
221
|
-
/* @__PURE__ */ p(
|
|
285
|
+
/* @__PURE__ */ p(r, {
|
|
222
286
|
title: "Approved",
|
|
223
|
-
value: String(
|
|
287
|
+
value: String(j?.approvedCount ?? 0),
|
|
224
288
|
description: "expenses approved"
|
|
225
289
|
}),
|
|
226
|
-
/* @__PURE__ */ p(
|
|
290
|
+
/* @__PURE__ */ p(r, {
|
|
227
291
|
title: "Average",
|
|
228
|
-
value: t(
|
|
229
|
-
icon: /* @__PURE__ */ p(
|
|
292
|
+
value: t(j?.averageAmount ?? 0),
|
|
293
|
+
icon: /* @__PURE__ */ p(u, { className: "h-5 w-5" }),
|
|
230
294
|
description: "per expense"
|
|
231
295
|
})
|
|
232
296
|
]
|
|
233
297
|
}),
|
|
234
|
-
|
|
298
|
+
A.length === 0 && !w ? /* @__PURE__ */ p(se, {
|
|
235
299
|
illustration: "empty-data",
|
|
236
300
|
title: "No expenses recorded yet",
|
|
237
301
|
description: "Add your first expense to track project costs and route them for approval.",
|
|
@@ -240,13 +304,13 @@ function b() {
|
|
|
240
304
|
className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop",
|
|
241
305
|
children: /* @__PURE__ */ p("div", {
|
|
242
306
|
className: "divide-y divide-border",
|
|
243
|
-
children:
|
|
244
|
-
let
|
|
307
|
+
children: A.map((e) => {
|
|
308
|
+
let r = Ue === e.id, i = e.status === s.Pending, a = e.status === s.Rejected;
|
|
245
309
|
return /* @__PURE__ */ m("div", {
|
|
246
310
|
className: "flex flex-col gap-3 p-4 hover:bg-bg-sunken/50 transition-colors sm:flex-row sm:items-center sm:justify-between",
|
|
247
311
|
children: [/* @__PURE__ */ m("div", {
|
|
248
312
|
className: "flex items-center gap-3",
|
|
249
|
-
children: [/* @__PURE__ */ p(
|
|
313
|
+
children: [/* @__PURE__ */ p(u, { className: "h-5 w-5 shrink-0 text-text-secondary" }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("p", {
|
|
250
314
|
className: "text-sm font-medium text-text-primary",
|
|
251
315
|
children: e.description ?? "Untitled expense"
|
|
252
316
|
}), /* @__PURE__ */ m("p", {
|
|
@@ -258,7 +322,7 @@ function b() {
|
|
|
258
322
|
e.projectId ?? "No project",
|
|
259
323
|
" ·",
|
|
260
324
|
" ",
|
|
261
|
-
|
|
325
|
+
n(e.date)
|
|
262
326
|
]
|
|
263
327
|
})] })]
|
|
264
328
|
}), /* @__PURE__ */ m("div", {
|
|
@@ -268,51 +332,58 @@ function b() {
|
|
|
268
332
|
className: "text-sm font-medium text-text-primary",
|
|
269
333
|
children: t(e.amount, e.currency)
|
|
270
334
|
}),
|
|
271
|
-
/* @__PURE__ */ p(
|
|
335
|
+
/* @__PURE__ */ p(ce, {
|
|
272
336
|
label: e.status.toLowerCase(),
|
|
273
337
|
status: e.status.toLowerCase()
|
|
274
338
|
}),
|
|
275
339
|
/* @__PURE__ */ m("div", {
|
|
276
340
|
className: "flex items-center gap-1",
|
|
277
341
|
children: [
|
|
278
|
-
|
|
342
|
+
i && /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
|
|
279
343
|
type: "button",
|
|
280
|
-
disabled:
|
|
281
|
-
onClick: () =>
|
|
344
|
+
disabled: r,
|
|
345
|
+
onClick: () => Ye(e),
|
|
282
346
|
title: "Approve expense",
|
|
283
347
|
className: "rounded-md p-1.5 text-status-success-text hover:bg-status-success-bg disabled:opacity-50",
|
|
284
|
-
children: /* @__PURE__ */ p(
|
|
348
|
+
children: /* @__PURE__ */ p(de, { className: "h-4 w-4" })
|
|
285
349
|
}), /* @__PURE__ */ p("button", {
|
|
286
350
|
type: "button",
|
|
287
|
-
disabled:
|
|
351
|
+
disabled: r,
|
|
288
352
|
onClick: () => {
|
|
289
353
|
K(e), J("");
|
|
290
354
|
},
|
|
291
355
|
title: "Reject expense",
|
|
292
356
|
className: "rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg disabled:opacity-50",
|
|
293
|
-
children: /* @__PURE__ */ p(
|
|
357
|
+
children: /* @__PURE__ */ p(be, { className: "h-4 w-4" })
|
|
294
358
|
})] }),
|
|
295
|
-
|
|
359
|
+
a && /* @__PURE__ */ p("button", {
|
|
296
360
|
type: "button",
|
|
297
|
-
disabled:
|
|
298
|
-
onClick: () =>
|
|
361
|
+
disabled: r,
|
|
362
|
+
onClick: () => Je(e),
|
|
299
363
|
title: "Resubmit for approval",
|
|
300
364
|
className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken disabled:opacity-50",
|
|
365
|
+
children: /* @__PURE__ */ p(_e, { className: "h-4 w-4" })
|
|
366
|
+
}),
|
|
367
|
+
/* @__PURE__ */ p("button", {
|
|
368
|
+
type: "button",
|
|
369
|
+
onClick: () => H(e),
|
|
370
|
+
title: "Review expense",
|
|
371
|
+
className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken",
|
|
301
372
|
children: /* @__PURE__ */ p(me, { className: "h-4 w-4" })
|
|
302
373
|
}),
|
|
303
374
|
/* @__PURE__ */ p("button", {
|
|
304
375
|
type: "button",
|
|
305
|
-
onClick: () =>
|
|
376
|
+
onClick: () => Ge(e),
|
|
306
377
|
title: "Edit expense",
|
|
307
378
|
className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken",
|
|
308
|
-
children: /* @__PURE__ */ p(
|
|
379
|
+
children: /* @__PURE__ */ p(he, { className: "h-4 w-4" })
|
|
309
380
|
}),
|
|
310
381
|
/* @__PURE__ */ p("button", {
|
|
311
382
|
type: "button",
|
|
312
383
|
onClick: () => W(e),
|
|
313
384
|
title: "Delete expense",
|
|
314
385
|
className: "rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg",
|
|
315
|
-
children: /* @__PURE__ */ p(
|
|
386
|
+
children: /* @__PURE__ */ p(ve, { className: "h-4 w-4" })
|
|
316
387
|
})
|
|
317
388
|
]
|
|
318
389
|
})
|
|
@@ -321,29 +392,29 @@ function b() {
|
|
|
321
392
|
}, e.id);
|
|
322
393
|
})
|
|
323
394
|
})
|
|
324
|
-
}),
|
|
395
|
+
}), Be && A.length >= _ && /* @__PURE__ */ p("div", {
|
|
325
396
|
className: "mt-4 flex justify-center",
|
|
326
397
|
children: /* @__PURE__ */ p("button", {
|
|
327
|
-
onClick:
|
|
328
|
-
disabled:
|
|
398
|
+
onClick: Ze,
|
|
399
|
+
disabled: M,
|
|
329
400
|
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",
|
|
330
|
-
children:
|
|
401
|
+
children: M ? "Loading..." : "Load more"
|
|
331
402
|
})
|
|
332
403
|
})] }),
|
|
333
|
-
/* @__PURE__ */ p(
|
|
334
|
-
open:
|
|
404
|
+
/* @__PURE__ */ p(c, {
|
|
405
|
+
open: He,
|
|
335
406
|
onClose: Z,
|
|
336
|
-
title:
|
|
337
|
-
description:
|
|
407
|
+
title: F ? "Edit Expense" : "Add Expense",
|
|
408
|
+
description: F ? "Update this expense's details." : "Record a project-related cost to track alongside your logged hours.",
|
|
338
409
|
footer: /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
|
|
339
410
|
onClick: Z,
|
|
340
411
|
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
341
412
|
children: "Cancel"
|
|
342
413
|
}), /* @__PURE__ */ p("button", {
|
|
343
414
|
onClick: Q,
|
|
344
|
-
disabled:
|
|
415
|
+
disabled: E || D || !L.amount || Number.isNaN(Number(L.amount)) || !L.category.trim(),
|
|
345
416
|
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",
|
|
346
|
-
children:
|
|
417
|
+
children: E || D ? "Saving..." : F ? "Save Changes" : "Add Expense"
|
|
347
418
|
})] }),
|
|
348
419
|
children: /* @__PURE__ */ m("div", {
|
|
349
420
|
className: "space-y-4",
|
|
@@ -358,23 +429,23 @@ function b() {
|
|
|
358
429
|
min: 0,
|
|
359
430
|
step: "0.01",
|
|
360
431
|
placeholder: "0.00",
|
|
361
|
-
value:
|
|
362
|
-
onChange: (e) =>
|
|
363
|
-
...
|
|
432
|
+
value: L.amount,
|
|
433
|
+
onChange: (e) => R({
|
|
434
|
+
...L,
|
|
364
435
|
amount: e.target.value
|
|
365
436
|
}),
|
|
366
|
-
className:
|
|
437
|
+
className: y
|
|
367
438
|
})] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
368
439
|
className: "block text-sm font-medium text-text-primary",
|
|
369
440
|
children: "Category"
|
|
370
441
|
}), /* @__PURE__ */ p("select", {
|
|
371
|
-
value:
|
|
372
|
-
onChange: (e) =>
|
|
373
|
-
...
|
|
442
|
+
value: L.category,
|
|
443
|
+
onChange: (e) => R({
|
|
444
|
+
...L,
|
|
374
445
|
category: e.target.value
|
|
375
446
|
}),
|
|
376
|
-
className:
|
|
377
|
-
children:
|
|
447
|
+
className: y,
|
|
448
|
+
children: v.map((e) => /* @__PURE__ */ p("option", {
|
|
378
449
|
value: e,
|
|
379
450
|
children: e
|
|
380
451
|
}, e))
|
|
@@ -386,12 +457,12 @@ function b() {
|
|
|
386
457
|
}), /* @__PURE__ */ p("input", {
|
|
387
458
|
type: "text",
|
|
388
459
|
placeholder: "What was this expense for?",
|
|
389
|
-
value:
|
|
390
|
-
onChange: (e) =>
|
|
391
|
-
...
|
|
460
|
+
value: L.description,
|
|
461
|
+
onChange: (e) => R({
|
|
462
|
+
...L,
|
|
392
463
|
description: e.target.value
|
|
393
464
|
}),
|
|
394
|
-
className:
|
|
465
|
+
className: y
|
|
395
466
|
})] }),
|
|
396
467
|
/* @__PURE__ */ m("div", {
|
|
397
468
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
@@ -401,29 +472,65 @@ function b() {
|
|
|
401
472
|
}), /* @__PURE__ */ p("input", {
|
|
402
473
|
type: "text",
|
|
403
474
|
placeholder: "Optional project ID",
|
|
404
|
-
value:
|
|
405
|
-
onChange: (e) =>
|
|
406
|
-
...
|
|
475
|
+
value: L.projectId,
|
|
476
|
+
onChange: (e) => R({
|
|
477
|
+
...L,
|
|
407
478
|
projectId: e.target.value
|
|
408
479
|
}),
|
|
409
|
-
className:
|
|
480
|
+
className: y
|
|
410
481
|
})] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
411
482
|
className: "block text-sm font-medium text-text-primary",
|
|
412
483
|
children: "Date"
|
|
413
484
|
}), /* @__PURE__ */ p("input", {
|
|
414
485
|
type: "date",
|
|
415
|
-
value:
|
|
416
|
-
onChange: (e) =>
|
|
417
|
-
...
|
|
486
|
+
value: L.date,
|
|
487
|
+
onChange: (e) => R({
|
|
488
|
+
...L,
|
|
418
489
|
date: e.target.value
|
|
419
490
|
}),
|
|
420
|
-
className:
|
|
491
|
+
className: y
|
|
421
492
|
})] })]
|
|
422
|
-
})
|
|
493
|
+
}),
|
|
494
|
+
/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
495
|
+
className: "block text-sm font-medium text-text-primary",
|
|
496
|
+
children: "Receipt"
|
|
497
|
+
}), /* @__PURE__ */ m("div", {
|
|
498
|
+
className: "mt-1 flex flex-col gap-2",
|
|
499
|
+
children: [
|
|
500
|
+
/* @__PURE__ */ m("label", {
|
|
501
|
+
className: `inline-flex w-fit cursor-pointer items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 ${z ? "opacity-50" : ""}`,
|
|
502
|
+
children: [
|
|
503
|
+
/* @__PURE__ */ p(ye, { className: "h-4 w-4" }),
|
|
504
|
+
z ? "Uploading..." : "Upload receipt",
|
|
505
|
+
/* @__PURE__ */ p("input", {
|
|
506
|
+
type: "file",
|
|
507
|
+
accept: "image/*,application/pdf",
|
|
508
|
+
disabled: z,
|
|
509
|
+
className: "hidden",
|
|
510
|
+
onChange: (e) => {
|
|
511
|
+
let t = e.target.files?.[0];
|
|
512
|
+
e.target.value = "", t && Ke(t);
|
|
513
|
+
}
|
|
514
|
+
})
|
|
515
|
+
]
|
|
516
|
+
}),
|
|
517
|
+
L.receiptUrl && /* @__PURE__ */ m("a", {
|
|
518
|
+
href: L.receiptUrl,
|
|
519
|
+
target: "_blank",
|
|
520
|
+
rel: "noopener noreferrer",
|
|
521
|
+
className: "inline-flex w-fit items-center gap-1.5 text-sm font-medium text-action-primary-text/90 underline decoration-border-subtle underline-offset-2 hover:text-action-primary-text",
|
|
522
|
+
children: [/* @__PURE__ */ p(pe, { className: "h-3.5 w-3.5" }), "View current receipt"]
|
|
523
|
+
}),
|
|
524
|
+
/* @__PURE__ */ p("p", {
|
|
525
|
+
className: "text-xs text-text-secondary",
|
|
526
|
+
children: "Attach an image or PDF. Uploading is optional and never blocks saving the expense."
|
|
527
|
+
})
|
|
528
|
+
]
|
|
529
|
+
})] })
|
|
423
530
|
]
|
|
424
531
|
})
|
|
425
532
|
}),
|
|
426
|
-
/* @__PURE__ */ p(
|
|
533
|
+
/* @__PURE__ */ p(c, {
|
|
427
534
|
open: U !== null,
|
|
428
535
|
onClose: () => W(null),
|
|
429
536
|
title: "Delete expense?",
|
|
@@ -434,17 +541,17 @@ function b() {
|
|
|
434
541
|
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
435
542
|
children: "Cancel"
|
|
436
543
|
}), /* @__PURE__ */ p("button", {
|
|
437
|
-
onClick:
|
|
438
|
-
disabled:
|
|
544
|
+
onClick: qe,
|
|
545
|
+
disabled: O,
|
|
439
546
|
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",
|
|
440
|
-
children:
|
|
547
|
+
children: O ? "Deleting..." : "Delete"
|
|
441
548
|
})] }),
|
|
442
549
|
children: /* @__PURE__ */ p("p", {
|
|
443
550
|
className: "text-sm text-text-secondary",
|
|
444
551
|
children: "This permanently removes the expense. This action cannot be undone."
|
|
445
552
|
})
|
|
446
553
|
}),
|
|
447
|
-
/* @__PURE__ */ m(
|
|
554
|
+
/* @__PURE__ */ m(c, {
|
|
448
555
|
open: G !== null,
|
|
449
556
|
onClose: () => K(null),
|
|
450
557
|
title: "Reject expense",
|
|
@@ -455,10 +562,10 @@ function b() {
|
|
|
455
562
|
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
456
563
|
children: "Cancel"
|
|
457
564
|
}), /* @__PURE__ */ p("button", {
|
|
458
|
-
onClick:
|
|
459
|
-
disabled:
|
|
565
|
+
onClick: Xe,
|
|
566
|
+
disabled: k,
|
|
460
567
|
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",
|
|
461
|
-
children:
|
|
568
|
+
children: k ? "Rejecting..." : "Reject Expense"
|
|
462
569
|
})] }),
|
|
463
570
|
children: [/* @__PURE__ */ p("label", {
|
|
464
571
|
className: "block text-sm font-medium text-text-primary",
|
|
@@ -468,17 +575,24 @@ function b() {
|
|
|
468
575
|
placeholder: "Reason for rejection (optional)",
|
|
469
576
|
value: q,
|
|
470
577
|
onChange: (e) => J(e.target.value),
|
|
471
|
-
className:
|
|
578
|
+
className: y
|
|
472
579
|
})]
|
|
473
580
|
}),
|
|
474
|
-
/* @__PURE__ */ p(
|
|
475
|
-
|
|
476
|
-
|
|
581
|
+
/* @__PURE__ */ p(c, {
|
|
582
|
+
open: V !== null,
|
|
583
|
+
onClose: () => H(null),
|
|
584
|
+
title: "Expense review",
|
|
585
|
+
description: V ? `Discuss ${V.description ?? "this expense"} with your team.` : void 0,
|
|
586
|
+
children: V && /* @__PURE__ */ p(Ee, { expense: V })
|
|
587
|
+
}),
|
|
588
|
+
/* @__PURE__ */ p(le, {
|
|
589
|
+
toasts: h,
|
|
590
|
+
onDismiss: S
|
|
477
591
|
})
|
|
478
592
|
]
|
|
479
593
|
});
|
|
480
594
|
}
|
|
481
595
|
//#endregion
|
|
482
|
-
export {
|
|
596
|
+
export { S as ExpenseTrackingPage };
|
|
483
597
|
|
|
484
598
|
//# sourceMappingURL=ExpenseTrackingPage.js.map
|