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