@burdenoff/microfe-timecampus 2026.703.1 → 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/generated/wspace-types.js +28 -0
- package/dist/generated/wspace-types.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/timecampus/components/Modal.js +65 -0
- package/dist/timecampus/components/Modal.js.map +1 -0
- package/dist/timecampus/components/StatusBadge.js +28 -0
- package/dist/timecampus/components/StatusBadge.js.map +1 -0
- package/dist/timecampus/components/Toast.js +87 -0
- package/dist/timecampus/components/Toast.js.map +1 -0
- package/dist/timecampus/index.js +1 -1
- package/dist/timecampus/pages/HomePage.js +143 -135
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/applets/AppletStorePage.js +14 -19
- package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js +49 -37
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +315 -87
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/channels/ChannelsPage.js +340 -46
- package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +432 -118
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js +70 -37
- package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +366 -60
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +562 -85
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/geofencing/GeofencingPage.js +314 -55
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +30 -27
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +269 -74
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js +127 -0
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -0
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js +62 -52
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +361 -74
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingFormModal.js +208 -0
- package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -0
- package/dist/timecampus/pages/meetings/MeetingsPage.js +175 -74
- package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js +105 -0
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -0
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js +307 -67
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectCreatePage.js +51 -48
- package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectDetailPage.js +248 -51
- package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectsPage.js +354 -87
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js +367 -65
- package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +674 -104
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js +46 -34
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js +93 -69
- package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamPage.js +4 -3
- package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
- package/dist/timecampus/pages/templates/TemplateDetailPage.js +72 -53
- package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
- package/dist/timecampus/pages/templates/TemplatesPage.js +40 -35
- package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeEntriesPage.js +484 -97
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +210 -99
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +229 -97
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +339 -110
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js +158 -76
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
- package/dist/timecampus/pages/widgets/WidgetsPage.js +181 -52
- package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
- package/dist/timecampus/pages/workflows/WorkflowsPage.js +212 -61
- package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
- package/dist/timecampus/store/timecampusStore.js +13 -1
- package/dist/timecampus/store/timecampusStore.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,110 +1,587 @@
|
|
|
1
1
|
import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
|
|
2
|
-
import { formatCurrency as t, formatDate as n
|
|
3
|
-
import { StatCard as
|
|
4
|
-
import { LoadingSkeleton as
|
|
5
|
-
import { useGetExpenseStatsQuery as
|
|
6
|
-
import { PageLayout as
|
|
7
|
-
import { EmptyState as
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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";
|
|
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";
|
|
10
20
|
//#region src/timecampus/pages/expense-tracking/ExpenseTrackingPage.tsx
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
var _ = 25, v = [
|
|
22
|
+
"Travel",
|
|
23
|
+
"Meals",
|
|
24
|
+
"Software",
|
|
25
|
+
"Hardware",
|
|
26
|
+
"Office",
|
|
27
|
+
"Training",
|
|
28
|
+
"Other"
|
|
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() {
|
|
42
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
43
|
+
}
|
|
44
|
+
var x = {
|
|
45
|
+
amount: "",
|
|
46
|
+
category: v[0],
|
|
47
|
+
description: "",
|
|
48
|
+
projectId: "",
|
|
49
|
+
date: b(),
|
|
50
|
+
receiptUrl: ""
|
|
51
|
+
};
|
|
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({
|
|
54
|
+
variables: {
|
|
55
|
+
workspaceId: d,
|
|
56
|
+
limit: _,
|
|
57
|
+
offset: 0
|
|
58
|
+
},
|
|
59
|
+
notifyOnNetworkStatusChange: !0
|
|
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({
|
|
69
|
+
amount: String(e.amount),
|
|
70
|
+
category: e.category,
|
|
71
|
+
description: e.description ?? "",
|
|
72
|
+
projectId: e.projectId ?? "",
|
|
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();
|
|
115
|
+
try {
|
|
116
|
+
F ? (await Pe({ variables: {
|
|
117
|
+
id: F.id,
|
|
118
|
+
workspaceId: d,
|
|
119
|
+
input: {
|
|
120
|
+
amount: e,
|
|
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
|
|
126
|
+
}
|
|
127
|
+
} }), g({
|
|
128
|
+
title: "Expense updated",
|
|
129
|
+
variant: "success"
|
|
130
|
+
})) : (await Ne({ variables: { input: {
|
|
131
|
+
workspaceId: d,
|
|
132
|
+
amount: e,
|
|
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({
|
|
139
|
+
title: "Expense added",
|
|
140
|
+
variant: "success"
|
|
141
|
+
})), Q(), await X();
|
|
142
|
+
} catch {
|
|
143
|
+
g({
|
|
144
|
+
title: F ? "Failed to update expense" : "Failed to add expense",
|
|
145
|
+
variant: "error"
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}, Ke = async () => {
|
|
149
|
+
if (U) try {
|
|
150
|
+
await Fe({ variables: {
|
|
151
|
+
id: U.id,
|
|
152
|
+
workspaceId: d
|
|
153
|
+
} }), g({
|
|
154
|
+
title: "Expense deleted",
|
|
155
|
+
variant: "success"
|
|
156
|
+
}), W(null), await X();
|
|
157
|
+
} catch {
|
|
158
|
+
g({
|
|
159
|
+
title: "Failed to delete expense",
|
|
160
|
+
variant: "error"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}, qe = async (e) => {
|
|
164
|
+
Y(e.id);
|
|
165
|
+
try {
|
|
166
|
+
await Ie({ variables: {
|
|
167
|
+
id: e.id,
|
|
168
|
+
workspaceId: d
|
|
169
|
+
} }), g({
|
|
170
|
+
title: "Submitted for approval",
|
|
171
|
+
variant: "success"
|
|
172
|
+
}), await X();
|
|
173
|
+
} catch {
|
|
174
|
+
g({
|
|
175
|
+
title: "Failed to submit expense",
|
|
176
|
+
variant: "error"
|
|
177
|
+
});
|
|
178
|
+
} finally {
|
|
179
|
+
Y(null);
|
|
180
|
+
}
|
|
181
|
+
}, Je = async (e) => {
|
|
182
|
+
Y(e.id);
|
|
183
|
+
try {
|
|
184
|
+
await Le({ variables: {
|
|
185
|
+
id: e.id,
|
|
186
|
+
workspaceId: d
|
|
187
|
+
} }), g({
|
|
188
|
+
title: "Expense approved",
|
|
189
|
+
variant: "success"
|
|
190
|
+
}), await X();
|
|
191
|
+
} catch {
|
|
192
|
+
g({
|
|
193
|
+
title: "Failed to approve expense",
|
|
194
|
+
variant: "error"
|
|
195
|
+
});
|
|
196
|
+
} finally {
|
|
197
|
+
Y(null);
|
|
198
|
+
}
|
|
199
|
+
}, Ye = async () => {
|
|
200
|
+
if (G) try {
|
|
201
|
+
await Re({ variables: {
|
|
202
|
+
id: G.id,
|
|
203
|
+
workspaceId: d,
|
|
204
|
+
reason: q.trim() || void 0
|
|
205
|
+
} }), g({
|
|
206
|
+
title: "Expense rejected",
|
|
207
|
+
variant: "success"
|
|
208
|
+
}), K(null), J(""), await X();
|
|
209
|
+
} catch {
|
|
210
|
+
g({
|
|
211
|
+
title: "Failed to reject expense",
|
|
212
|
+
variant: "error"
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}, Xe = async () => {
|
|
216
|
+
N(!0);
|
|
217
|
+
try {
|
|
218
|
+
((await T({
|
|
219
|
+
variables: {
|
|
220
|
+
workspaceId: d,
|
|
221
|
+
limit: _,
|
|
222
|
+
offset: A.length
|
|
223
|
+
},
|
|
224
|
+
updateQuery: (e, { fetchMoreResult: t }) => t ? {
|
|
225
|
+
...e,
|
|
226
|
+
expenses: [...e.expenses, ...t.expenses]
|
|
227
|
+
} : e
|
|
228
|
+
})).data?.expenses?.length ?? 0) < _ && Be(!1);
|
|
229
|
+
} finally {
|
|
230
|
+
N(!1);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
if (w && Ae && A.length === 0) return /* @__PURE__ */ p(o, {
|
|
14
234
|
title: "Expense Tracking",
|
|
15
235
|
description: "Track and manage work expenses",
|
|
16
|
-
children: /* @__PURE__ */ p(
|
|
236
|
+
children: /* @__PURE__ */ p(i, {
|
|
17
237
|
variant: "list",
|
|
18
238
|
count: 4
|
|
19
239
|
})
|
|
20
|
-
})
|
|
240
|
+
});
|
|
241
|
+
if (De && je) return /* @__PURE__ */ p(o, {
|
|
21
242
|
title: "Expense Tracking",
|
|
22
243
|
description: "Track and manage work expenses",
|
|
23
244
|
children: /* @__PURE__ */ p("div", {
|
|
24
245
|
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
25
246
|
children: "Failed to load expense data. Please try again later."
|
|
26
247
|
})
|
|
27
|
-
})
|
|
248
|
+
});
|
|
249
|
+
let $ = /* @__PURE__ */ m("button", {
|
|
250
|
+
onClick: Ue,
|
|
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",
|
|
252
|
+
children: [/* @__PURE__ */ p(ge, { className: "h-4 w-4" }), "Add Expense"]
|
|
253
|
+
});
|
|
254
|
+
return /* @__PURE__ */ m(o, {
|
|
28
255
|
title: "Expense Tracking",
|
|
29
256
|
description: "Track and manage work expenses",
|
|
30
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.",
|
|
31
|
-
actions:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
258
|
+
actions: $,
|
|
259
|
+
children: [
|
|
260
|
+
/* @__PURE__ */ m("div", {
|
|
261
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
262
|
+
children: [
|
|
263
|
+
/* @__PURE__ */ p(r, {
|
|
264
|
+
featured: !0,
|
|
265
|
+
title: "Total This Month",
|
|
266
|
+
value: t(j?.totalAmount ?? 0),
|
|
267
|
+
icon: /* @__PURE__ */ p(fe, { className: "h-5 w-5" })
|
|
268
|
+
}),
|
|
269
|
+
/* @__PURE__ */ p(r, {
|
|
270
|
+
title: "Pending",
|
|
271
|
+
value: String(j?.pendingCount ?? 0),
|
|
272
|
+
description: "expenses awaiting approval"
|
|
273
|
+
}),
|
|
274
|
+
/* @__PURE__ */ p(r, {
|
|
275
|
+
title: "Approved",
|
|
276
|
+
value: String(j?.approvedCount ?? 0),
|
|
277
|
+
description: "expenses approved"
|
|
278
|
+
}),
|
|
279
|
+
/* @__PURE__ */ p(r, {
|
|
280
|
+
title: "Average",
|
|
281
|
+
value: t(j?.averageAmount ?? 0),
|
|
282
|
+
icon: /* @__PURE__ */ p(u, { className: "h-5 w-5" }),
|
|
283
|
+
description: "per expense"
|
|
284
|
+
})
|
|
285
|
+
]
|
|
286
|
+
}),
|
|
287
|
+
A.length === 0 && !w ? /* @__PURE__ */ p(se, {
|
|
288
|
+
illustration: "empty-data",
|
|
289
|
+
title: "No expenses recorded yet",
|
|
290
|
+
description: "Add your first expense to track project costs and route them for approval.",
|
|
291
|
+
action: $
|
|
292
|
+
}) : /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("div", {
|
|
293
|
+
className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop",
|
|
294
|
+
children: /* @__PURE__ */ p("div", {
|
|
295
|
+
className: "divide-y divide-border",
|
|
296
|
+
children: A.map((e) => {
|
|
297
|
+
let r = He === e.id, i = e.status === s.Pending, a = e.status === s.Rejected;
|
|
298
|
+
return /* @__PURE__ */ m("div", {
|
|
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",
|
|
300
|
+
children: [/* @__PURE__ */ m("div", {
|
|
301
|
+
className: "flex items-center gap-3",
|
|
302
|
+
children: [/* @__PURE__ */ p(u, { className: "h-5 w-5 shrink-0 text-text-secondary" }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("p", {
|
|
303
|
+
className: "text-sm font-medium text-text-primary",
|
|
304
|
+
children: e.description ?? "Untitled expense"
|
|
305
|
+
}), /* @__PURE__ */ m("p", {
|
|
306
|
+
className: "text-xs text-text-secondary",
|
|
307
|
+
children: [
|
|
308
|
+
e.category,
|
|
309
|
+
" ·",
|
|
310
|
+
" ",
|
|
311
|
+
e.projectId ?? "No project",
|
|
312
|
+
" ·",
|
|
313
|
+
" ",
|
|
314
|
+
n(e.date)
|
|
315
|
+
]
|
|
316
|
+
})] })]
|
|
317
|
+
}), /* @__PURE__ */ m("div", {
|
|
318
|
+
className: "flex items-center gap-3",
|
|
319
|
+
children: [
|
|
320
|
+
/* @__PURE__ */ p("p", {
|
|
321
|
+
className: "text-sm font-medium text-text-primary",
|
|
322
|
+
children: t(e.amount, e.currency)
|
|
323
|
+
}),
|
|
324
|
+
/* @__PURE__ */ p(ce, {
|
|
325
|
+
label: e.status.toLowerCase(),
|
|
326
|
+
status: e.status.toLowerCase()
|
|
327
|
+
}),
|
|
328
|
+
/* @__PURE__ */ m("div", {
|
|
329
|
+
className: "flex items-center gap-1",
|
|
330
|
+
children: [
|
|
331
|
+
i && /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
|
|
332
|
+
type: "button",
|
|
333
|
+
disabled: r,
|
|
334
|
+
onClick: () => Je(e),
|
|
335
|
+
title: "Approve expense",
|
|
336
|
+
className: "rounded-md p-1.5 text-status-success-text hover:bg-status-success-bg disabled:opacity-50",
|
|
337
|
+
children: /* @__PURE__ */ p(de, { className: "h-4 w-4" })
|
|
338
|
+
}), /* @__PURE__ */ p("button", {
|
|
339
|
+
type: "button",
|
|
340
|
+
disabled: r,
|
|
341
|
+
onClick: () => {
|
|
342
|
+
K(e), J("");
|
|
343
|
+
},
|
|
344
|
+
title: "Reject expense",
|
|
345
|
+
className: "rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg disabled:opacity-50",
|
|
346
|
+
children: /* @__PURE__ */ p(be, { className: "h-4 w-4" })
|
|
347
|
+
})] }),
|
|
348
|
+
a && /* @__PURE__ */ p("button", {
|
|
349
|
+
type: "button",
|
|
350
|
+
disabled: r,
|
|
351
|
+
onClick: () => qe(e),
|
|
352
|
+
title: "Resubmit for approval",
|
|
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",
|
|
361
|
+
children: /* @__PURE__ */ p(me, { className: "h-4 w-4" })
|
|
362
|
+
}),
|
|
363
|
+
/* @__PURE__ */ p("button", {
|
|
364
|
+
type: "button",
|
|
365
|
+
onClick: () => Z(e),
|
|
366
|
+
title: "Edit expense",
|
|
367
|
+
className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken",
|
|
368
|
+
children: /* @__PURE__ */ p(he, { className: "h-4 w-4" })
|
|
369
|
+
}),
|
|
370
|
+
/* @__PURE__ */ p("button", {
|
|
371
|
+
type: "button",
|
|
372
|
+
onClick: () => W(e),
|
|
373
|
+
title: "Delete expense",
|
|
374
|
+
className: "rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg",
|
|
375
|
+
children: /* @__PURE__ */ p(ve, { className: "h-4 w-4" })
|
|
376
|
+
})
|
|
377
|
+
]
|
|
378
|
+
})
|
|
379
|
+
]
|
|
380
|
+
})]
|
|
381
|
+
}, e.id);
|
|
382
|
+
})
|
|
59
383
|
})
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
384
|
+
}), ze && A.length >= _ && /* @__PURE__ */ p("div", {
|
|
385
|
+
className: "mt-4 flex justify-center",
|
|
386
|
+
children: /* @__PURE__ */ p("button", {
|
|
387
|
+
onClick: Xe,
|
|
388
|
+
disabled: M,
|
|
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",
|
|
390
|
+
children: M ? "Loading..." : "Load more"
|
|
391
|
+
})
|
|
392
|
+
})] }),
|
|
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.",
|
|
398
|
+
footer: /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
|
|
399
|
+
onClick: Q,
|
|
400
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
401
|
+
children: "Cancel"
|
|
402
|
+
}), /* @__PURE__ */ p("button", {
|
|
403
|
+
onClick: Ge,
|
|
404
|
+
disabled: E || D || !L.amount || Number.isNaN(Number(L.amount)) || !L.category.trim(),
|
|
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",
|
|
406
|
+
children: E || D ? "Saving..." : F ? "Save Changes" : "Add Expense"
|
|
407
|
+
})] }),
|
|
408
|
+
children: /* @__PURE__ */ m("div", {
|
|
409
|
+
className: "space-y-4",
|
|
410
|
+
children: [
|
|
411
|
+
/* @__PURE__ */ m("div", {
|
|
412
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
413
|
+
children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
414
|
+
className: "block text-sm font-medium text-text-primary",
|
|
415
|
+
children: "Amount"
|
|
416
|
+
}), /* @__PURE__ */ p("input", {
|
|
417
|
+
type: "number",
|
|
418
|
+
min: 0,
|
|
419
|
+
step: "0.01",
|
|
420
|
+
placeholder: "0.00",
|
|
421
|
+
value: L.amount,
|
|
422
|
+
onChange: (e) => R({
|
|
423
|
+
...L,
|
|
424
|
+
amount: e.target.value
|
|
425
|
+
}),
|
|
426
|
+
className: y
|
|
427
|
+
})] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
428
|
+
className: "block text-sm font-medium text-text-primary",
|
|
429
|
+
children: "Category"
|
|
430
|
+
}), /* @__PURE__ */ p("select", {
|
|
431
|
+
value: L.category,
|
|
432
|
+
onChange: (e) => R({
|
|
433
|
+
...L,
|
|
434
|
+
category: e.target.value
|
|
435
|
+
}),
|
|
436
|
+
className: y,
|
|
437
|
+
children: v.map((e) => /* @__PURE__ */ p("option", {
|
|
438
|
+
value: e,
|
|
439
|
+
children: e
|
|
440
|
+
}, e))
|
|
441
|
+
})] })]
|
|
442
|
+
}),
|
|
443
|
+
/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
444
|
+
className: "block text-sm font-medium text-text-primary",
|
|
445
|
+
children: "Description"
|
|
446
|
+
}), /* @__PURE__ */ p("input", {
|
|
447
|
+
type: "text",
|
|
448
|
+
placeholder: "What was this expense for?",
|
|
449
|
+
value: L.description,
|
|
450
|
+
onChange: (e) => R({
|
|
451
|
+
...L,
|
|
452
|
+
description: e.target.value
|
|
453
|
+
}),
|
|
454
|
+
className: y
|
|
455
|
+
})] }),
|
|
456
|
+
/* @__PURE__ */ m("div", {
|
|
457
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
458
|
+
children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
459
|
+
className: "block text-sm font-medium text-text-primary",
|
|
460
|
+
children: "Project ID"
|
|
461
|
+
}), /* @__PURE__ */ p("input", {
|
|
462
|
+
type: "text",
|
|
463
|
+
placeholder: "Optional project ID",
|
|
464
|
+
value: L.projectId,
|
|
465
|
+
onChange: (e) => R({
|
|
466
|
+
...L,
|
|
467
|
+
projectId: e.target.value
|
|
468
|
+
}),
|
|
469
|
+
className: y
|
|
470
|
+
})] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
471
|
+
className: "block text-sm font-medium text-text-primary",
|
|
472
|
+
children: "Date"
|
|
473
|
+
}), /* @__PURE__ */ p("input", {
|
|
474
|
+
type: "date",
|
|
475
|
+
value: L.date,
|
|
476
|
+
onChange: (e) => R({
|
|
477
|
+
...L,
|
|
478
|
+
date: e.target.value
|
|
479
|
+
}),
|
|
480
|
+
className: y
|
|
481
|
+
})] })]
|
|
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",
|
|
82
488
|
children: [
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
+
})
|
|
90
517
|
]
|
|
91
|
-
})] })
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
518
|
+
})] })
|
|
519
|
+
]
|
|
520
|
+
})
|
|
521
|
+
}),
|
|
522
|
+
/* @__PURE__ */ p(c, {
|
|
523
|
+
open: U !== null,
|
|
524
|
+
onClose: () => W(null),
|
|
525
|
+
title: "Delete expense?",
|
|
526
|
+
description: U ? `${t(U.amount, U.currency)} · ${U.category} will be removed.` : void 0,
|
|
527
|
+
size: "sm",
|
|
528
|
+
footer: /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
|
|
529
|
+
onClick: () => W(null),
|
|
530
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
531
|
+
children: "Cancel"
|
|
532
|
+
}), /* @__PURE__ */ p("button", {
|
|
533
|
+
onClick: Ke,
|
|
534
|
+
disabled: O,
|
|
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",
|
|
536
|
+
children: O ? "Deleting..." : "Delete"
|
|
537
|
+
})] }),
|
|
538
|
+
children: /* @__PURE__ */ p("p", {
|
|
539
|
+
className: "text-sm text-text-secondary",
|
|
540
|
+
children: "This permanently removes the expense. This action cannot be undone."
|
|
541
|
+
})
|
|
542
|
+
}),
|
|
543
|
+
/* @__PURE__ */ m(c, {
|
|
544
|
+
open: G !== null,
|
|
545
|
+
onClose: () => K(null),
|
|
546
|
+
title: "Reject expense",
|
|
547
|
+
description: "Let the submitter know why this expense was rejected.",
|
|
548
|
+
size: "sm",
|
|
549
|
+
footer: /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
|
|
550
|
+
onClick: () => K(null),
|
|
551
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
552
|
+
children: "Cancel"
|
|
553
|
+
}), /* @__PURE__ */ p("button", {
|
|
554
|
+
onClick: Ye,
|
|
555
|
+
disabled: k,
|
|
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",
|
|
557
|
+
children: k ? "Rejecting..." : "Reject Expense"
|
|
558
|
+
})] }),
|
|
559
|
+
children: [/* @__PURE__ */ p("label", {
|
|
560
|
+
className: "block text-sm font-medium text-text-primary",
|
|
561
|
+
children: "Reason"
|
|
562
|
+
}), /* @__PURE__ */ p("textarea", {
|
|
563
|
+
rows: 3,
|
|
564
|
+
placeholder: "Reason for rejection (optional)",
|
|
565
|
+
value: q,
|
|
566
|
+
onChange: (e) => J(e.target.value),
|
|
567
|
+
className: y
|
|
568
|
+
})]
|
|
569
|
+
}),
|
|
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
|
|
103
580
|
})
|
|
104
|
-
|
|
581
|
+
]
|
|
105
582
|
});
|
|
106
583
|
}
|
|
107
584
|
//#endregion
|
|
108
|
-
export {
|
|
585
|
+
export { S as ExpenseTrackingPage };
|
|
109
586
|
|
|
110
587
|
//# sourceMappingURL=ExpenseTrackingPage.js.map
|