@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.
@@ -1,19 +1,24 @@
1
1
  import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
2
- import { formatCurrency as t, formatDate as ee } from "../../utils/helpers.js";
3
- import { StatCard as n } from "../../components/StatCard.js";
4
- import { LoadingSkeleton as r } from "../../components/LoadingSkeleton.js";
5
- import { useApproveExpenseMutation as i, useCreateExpenseMutation as te, useDeleteExpenseMutation as ne, useGetExpenseStatsQuery as re, useListExpensesQuery as ie, useRejectExpenseMutation as ae, useSubmitExpenseForApprovalMutation as oe, useUpdateExpenseMutation as se } from "../../../generated/wspace-operations.js";
6
- import { PageLayout as a } from "../../components/PageLayout.js";
7
- import { EmptyState as ce } from "../../components/EmptyState.js";
8
- import { ExpenseStatus as o } from "../../../generated/wspace-types.js";
9
- import { Modal as s } from "../../components/Modal.js";
10
- import { StatusBadge as le } from "../../components/StatusBadge.js";
11
- import { ToastContainer as ue, useToast as de } from "../../components/Toast.js";
12
- import { useState as c } from "react";
13
- import { Check as l, DollarSign as u, Pencil as fe, Plus as pe, Receipt as d, Send as me, Trash2 as he, X as ge } from "lucide-react";
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 h = 25, g = [
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
- ], _ = "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";
25
- function v() {
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 y = {
50
+ var x = {
29
51
  amount: "",
30
- category: g[0],
52
+ category: v[0],
31
53
  description: "",
32
54
  projectId: "",
33
- date: v()
55
+ date: b(),
56
+ receiptUrl: ""
34
57
  };
35
- function b() {
36
- let { workspaceId: b = "" } = e(), { toasts: _e, addToast: x, dismissToast: ve } = de(), { data: ye, loading: S, error: be, refetch: xe, fetchMore: Se } = ie({
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: b,
39
- limit: h,
61
+ workspaceId: d,
62
+ limit: _,
40
63
  offset: 0
41
64
  },
42
65
  notifyOnNetworkStatusChange: !0
43
- }), { data: C, loading: w, error: T, refetch: E } = re({ variables: { workspaceId: b } }), [D, { loading: O }] = te(), [k, { loading: A }] = se(), [j, { loading: M }] = ne(), [Ce] = oe(), [we] = i(), [Te, { loading: N }] = ae(), P = ye?.expenses ?? [], F = C?.expenseStats, [I, L] = c(!1), [Ee, De] = c(!0), [Oe, R] = c(!1), [z, B] = c(null), [V, H] = c(y), [U, W] = c(null), [G, K] = c(null), [q, J] = c(""), [ke, Y] = c(null), X = async () => {
44
- await Promise.all([xe(), E()]);
45
- }, Ae = () => {
46
- B(null), H({
47
- ...y,
48
- date: v()
49
- }), R(!0);
50
- }, je = (e) => {
51
- B(e), H({
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) : v()
57
- }), R(!0);
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
- R(!1), B(null);
121
+ P(!1), I(null);
60
122
  }, Q = async () => {
61
- let e = Number(V.amount);
62
- if (!V.amount || Number.isNaN(e) || !V.category.trim()) return;
63
- let t = new Date(V.date).toISOString();
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
- z ? (await k({ variables: {
66
- id: z.id,
67
- workspaceId: b,
127
+ F ? (await Fe({ variables: {
128
+ id: F.id,
129
+ workspaceId: d,
68
130
  input: {
69
131
  amount: e,
70
- category: V.category.trim(),
71
- description: V.description.trim() || void 0,
72
- projectId: V.projectId.trim() || void 0,
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
- } }), x({
138
+ } }), g({
76
139
  title: "Expense updated",
77
140
  variant: "success"
78
- })) : (await D({ variables: { input: {
79
- workspaceId: b,
141
+ })) : (await Pe({ variables: { input: {
142
+ workspaceId: d,
80
143
  amount: e,
81
- category: V.category.trim(),
82
- description: V.description.trim() || void 0,
83
- projectId: V.projectId.trim() || void 0,
84
- date: t
85
- } } }), x({
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
- x({
91
- title: z ? "Failed to update expense" : "Failed to add expense",
154
+ g({
155
+ title: F ? "Failed to update expense" : "Failed to add expense",
92
156
  variant: "error"
93
157
  });
94
158
  }
95
- }, Me = async () => {
159
+ }, qe = async () => {
96
160
  if (U) try {
97
- await j({ variables: {
161
+ await Ie({ variables: {
98
162
  id: U.id,
99
- workspaceId: b
100
- } }), x({
163
+ workspaceId: d
164
+ } }), g({
101
165
  title: "Expense deleted",
102
166
  variant: "success"
103
167
  }), W(null), await X();
104
168
  } catch {
105
- x({
169
+ g({
106
170
  title: "Failed to delete expense",
107
171
  variant: "error"
108
172
  });
109
173
  }
110
- }, Ne = async (e) => {
174
+ }, Je = async (e) => {
111
175
  Y(e.id);
112
176
  try {
113
- await Ce({ variables: {
177
+ await Le({ variables: {
114
178
  id: e.id,
115
- workspaceId: b
116
- } }), x({
179
+ workspaceId: d
180
+ } }), g({
117
181
  title: "Submitted for approval",
118
182
  variant: "success"
119
183
  }), await X();
120
184
  } catch {
121
- x({
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
- }, Pe = async (e) => {
192
+ }, Ye = async (e) => {
129
193
  Y(e.id);
130
194
  try {
131
- await we({ variables: {
195
+ await Re({ variables: {
132
196
  id: e.id,
133
- workspaceId: b
134
- } }), x({
197
+ workspaceId: d
198
+ } }), g({
135
199
  title: "Expense approved",
136
200
  variant: "success"
137
201
  }), await X();
138
202
  } catch {
139
- x({
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
- }, Fe = async () => {
210
+ }, Xe = async () => {
147
211
  if (G) try {
148
- await Te({ variables: {
212
+ await ze({ variables: {
149
213
  id: G.id,
150
- workspaceId: b,
214
+ workspaceId: d,
151
215
  reason: q.trim() || void 0
152
- } }), x({
216
+ } }), g({
153
217
  title: "Expense rejected",
154
218
  variant: "success"
155
219
  }), K(null), J(""), await X();
156
220
  } catch {
157
- x({
221
+ g({
158
222
  title: "Failed to reject expense",
159
223
  variant: "error"
160
224
  });
161
225
  }
162
- }, Ie = async () => {
163
- L(!0);
226
+ }, Ze = async () => {
227
+ N(!0);
164
228
  try {
165
- ((await Se({
229
+ ((await ke({
166
230
  variables: {
167
- workspaceId: b,
168
- limit: h,
169
- offset: P.length
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) < h && De(!1);
239
+ })).data?.expenses?.length ?? 0) < _ && Ve(!1);
176
240
  } finally {
177
- L(!1);
241
+ N(!1);
178
242
  }
179
243
  };
180
- if (S && w && P.length === 0) return /* @__PURE__ */ p(a, {
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(r, {
247
+ children: /* @__PURE__ */ p(i, {
184
248
  variant: "list",
185
249
  count: 4
186
250
  })
187
251
  });
188
- if (be && T) return /* @__PURE__ */ p(a, {
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: Ae,
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(pe, { className: "h-4 w-4" }), "Add Expense"]
263
+ children: [/* @__PURE__ */ p(ge, { className: "h-4 w-4" }), "Add Expense"]
200
264
  });
201
- return /* @__PURE__ */ m(a, {
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(n, {
274
+ /* @__PURE__ */ p(r, {
211
275
  featured: !0,
212
276
  title: "Total This Month",
213
- value: t(F?.totalAmount ?? 0),
214
- icon: /* @__PURE__ */ p(u, { className: "h-5 w-5" })
277
+ value: t(j?.totalAmount ?? 0),
278
+ icon: /* @__PURE__ */ p(fe, { className: "h-5 w-5" })
215
279
  }),
216
- /* @__PURE__ */ p(n, {
280
+ /* @__PURE__ */ p(r, {
217
281
  title: "Pending",
218
- value: String(F?.pendingCount ?? 0),
282
+ value: String(j?.pendingCount ?? 0),
219
283
  description: "expenses awaiting approval"
220
284
  }),
221
- /* @__PURE__ */ p(n, {
285
+ /* @__PURE__ */ p(r, {
222
286
  title: "Approved",
223
- value: String(F?.approvedCount ?? 0),
287
+ value: String(j?.approvedCount ?? 0),
224
288
  description: "expenses approved"
225
289
  }),
226
- /* @__PURE__ */ p(n, {
290
+ /* @__PURE__ */ p(r, {
227
291
  title: "Average",
228
- value: t(F?.averageAmount ?? 0),
229
- icon: /* @__PURE__ */ p(d, { className: "h-5 w-5" }),
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
- P.length === 0 && !S ? /* @__PURE__ */ p(ce, {
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: P.map((e) => {
244
- let n = ke === e.id, r = e.status === o.Pending, i = e.status === o.Rejected;
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(d, { className: "h-5 w-5 shrink-0 text-text-secondary" }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("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
- ee(e.date)
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(le, {
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
- r && /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
342
+ i && /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
279
343
  type: "button",
280
- disabled: n,
281
- onClick: () => Pe(e),
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(l, { className: "h-4 w-4" })
348
+ children: /* @__PURE__ */ p(de, { className: "h-4 w-4" })
285
349
  }), /* @__PURE__ */ p("button", {
286
350
  type: "button",
287
- disabled: n,
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(ge, { className: "h-4 w-4" })
357
+ children: /* @__PURE__ */ p(be, { className: "h-4 w-4" })
294
358
  })] }),
295
- i && /* @__PURE__ */ p("button", {
359
+ a && /* @__PURE__ */ p("button", {
296
360
  type: "button",
297
- disabled: n,
298
- onClick: () => Ne(e),
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: () => je(e),
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(fe, { className: "h-4 w-4" })
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(he, { className: "h-4 w-4" })
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
- }), Ee && P.length >= h && /* @__PURE__ */ p("div", {
395
+ }), Be && A.length >= _ && /* @__PURE__ */ p("div", {
325
396
  className: "mt-4 flex justify-center",
326
397
  children: /* @__PURE__ */ p("button", {
327
- onClick: Ie,
328
- disabled: I,
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: I ? "Loading..." : "Load more"
401
+ children: M ? "Loading..." : "Load more"
331
402
  })
332
403
  })] }),
333
- /* @__PURE__ */ p(s, {
334
- open: Oe,
404
+ /* @__PURE__ */ p(c, {
405
+ open: He,
335
406
  onClose: Z,
336
- title: z ? "Edit Expense" : "Add Expense",
337
- description: z ? "Update this expense's details." : "Record a project-related cost to track alongside your logged hours.",
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: O || A || !V.amount || Number.isNaN(Number(V.amount)) || !V.category.trim(),
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: O || A ? "Saving..." : z ? "Save Changes" : "Add Expense"
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: V.amount,
362
- onChange: (e) => H({
363
- ...V,
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: V.category,
372
- onChange: (e) => H({
373
- ...V,
442
+ value: L.category,
443
+ onChange: (e) => R({
444
+ ...L,
374
445
  category: e.target.value
375
446
  }),
376
- className: _,
377
- children: g.map((e) => /* @__PURE__ */ p("option", {
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: V.description,
390
- onChange: (e) => H({
391
- ...V,
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: V.projectId,
405
- onChange: (e) => H({
406
- ...V,
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: V.date,
416
- onChange: (e) => H({
417
- ...V,
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(s, {
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: Me,
438
- disabled: M,
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: M ? "Deleting..." : "Delete"
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(s, {
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: Fe,
459
- disabled: N,
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: N ? "Rejecting..." : "Reject Expense"
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(ue, {
475
- toasts: _e,
476
- onDismiss: ve
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 { b as ExpenseTrackingPage };
596
+ export { S as ExpenseTrackingPage };
483
597
 
484
598
  //# sourceMappingURL=ExpenseTrackingPage.js.map