@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.
@@ -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,219 @@ 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 = 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 y = {
44
+ var x = {
29
45
  amount: "",
30
- category: g[0],
46
+ category: v[0],
31
47
  description: "",
32
48
  projectId: "",
33
- date: v()
49
+ date: b(),
50
+ receiptUrl: ""
34
51
  };
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({
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: b,
39
- limit: h,
55
+ workspaceId: d,
56
+ limit: _,
40
57
  offset: 0
41
58
  },
42
59
  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({
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) : v()
57
- }), R(!0);
58
- }, Z = () => {
59
- R(!1), B(null);
60
- }, 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();
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
- z ? (await k({ variables: {
66
- id: z.id,
67
- workspaceId: b,
116
+ F ? (await Pe({ variables: {
117
+ id: F.id,
118
+ workspaceId: d,
68
119
  input: {
69
120
  amount: e,
70
- category: V.category.trim(),
71
- description: V.description.trim() || void 0,
72
- projectId: V.projectId.trim() || void 0,
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
- } }), x({
127
+ } }), g({
76
128
  title: "Expense updated",
77
129
  variant: "success"
78
- })) : (await D({ variables: { input: {
79
- workspaceId: b,
130
+ })) : (await Ne({ variables: { input: {
131
+ workspaceId: d,
80
132
  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({
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
- })), Z(), await X();
141
+ })), Q(), await X();
89
142
  } catch {
90
- x({
91
- title: z ? "Failed to update expense" : "Failed to add expense",
143
+ g({
144
+ title: F ? "Failed to update expense" : "Failed to add expense",
92
145
  variant: "error"
93
146
  });
94
147
  }
95
- }, Me = async () => {
148
+ }, Ke = async () => {
96
149
  if (U) try {
97
- await j({ variables: {
150
+ await Fe({ variables: {
98
151
  id: U.id,
99
- workspaceId: b
100
- } }), x({
152
+ workspaceId: d
153
+ } }), g({
101
154
  title: "Expense deleted",
102
155
  variant: "success"
103
156
  }), W(null), await X();
104
157
  } catch {
105
- x({
158
+ g({
106
159
  title: "Failed to delete expense",
107
160
  variant: "error"
108
161
  });
109
162
  }
110
- }, Ne = async (e) => {
163
+ }, qe = async (e) => {
111
164
  Y(e.id);
112
165
  try {
113
- await Ce({ variables: {
166
+ await Ie({ variables: {
114
167
  id: e.id,
115
- workspaceId: b
116
- } }), x({
168
+ workspaceId: d
169
+ } }), g({
117
170
  title: "Submitted for approval",
118
171
  variant: "success"
119
172
  }), await X();
120
173
  } catch {
121
- x({
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
- }, Pe = async (e) => {
181
+ }, Je = async (e) => {
129
182
  Y(e.id);
130
183
  try {
131
- await we({ variables: {
184
+ await Le({ variables: {
132
185
  id: e.id,
133
- workspaceId: b
134
- } }), x({
186
+ workspaceId: d
187
+ } }), g({
135
188
  title: "Expense approved",
136
189
  variant: "success"
137
190
  }), await X();
138
191
  } catch {
139
- x({
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
- }, Fe = async () => {
199
+ }, Ye = async () => {
147
200
  if (G) try {
148
- await Te({ variables: {
201
+ await Re({ variables: {
149
202
  id: G.id,
150
- workspaceId: b,
203
+ workspaceId: d,
151
204
  reason: q.trim() || void 0
152
- } }), x({
205
+ } }), g({
153
206
  title: "Expense rejected",
154
207
  variant: "success"
155
208
  }), K(null), J(""), await X();
156
209
  } catch {
157
- x({
210
+ g({
158
211
  title: "Failed to reject expense",
159
212
  variant: "error"
160
213
  });
161
214
  }
162
- }, Ie = async () => {
163
- L(!0);
215
+ }, Xe = async () => {
216
+ N(!0);
164
217
  try {
165
- ((await Se({
218
+ ((await T({
166
219
  variables: {
167
- workspaceId: b,
168
- limit: h,
169
- offset: P.length
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) < h && De(!1);
228
+ })).data?.expenses?.length ?? 0) < _ && Be(!1);
176
229
  } finally {
177
- L(!1);
230
+ N(!1);
178
231
  }
179
232
  };
180
- if (S && w && P.length === 0) return /* @__PURE__ */ p(a, {
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(r, {
236
+ children: /* @__PURE__ */ p(i, {
184
237
  variant: "list",
185
238
  count: 4
186
239
  })
187
240
  });
188
- if (be && T) return /* @__PURE__ */ p(a, {
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: Ae,
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(pe, { className: "h-4 w-4" }), "Add Expense"]
252
+ children: [/* @__PURE__ */ p(ge, { className: "h-4 w-4" }), "Add Expense"]
200
253
  });
201
- return /* @__PURE__ */ m(a, {
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(n, {
263
+ /* @__PURE__ */ p(r, {
211
264
  featured: !0,
212
265
  title: "Total This Month",
213
- value: t(F?.totalAmount ?? 0),
214
- icon: /* @__PURE__ */ p(u, { className: "h-5 w-5" })
266
+ value: t(j?.totalAmount ?? 0),
267
+ icon: /* @__PURE__ */ p(fe, { className: "h-5 w-5" })
215
268
  }),
216
- /* @__PURE__ */ p(n, {
269
+ /* @__PURE__ */ p(r, {
217
270
  title: "Pending",
218
- value: String(F?.pendingCount ?? 0),
271
+ value: String(j?.pendingCount ?? 0),
219
272
  description: "expenses awaiting approval"
220
273
  }),
221
- /* @__PURE__ */ p(n, {
274
+ /* @__PURE__ */ p(r, {
222
275
  title: "Approved",
223
- value: String(F?.approvedCount ?? 0),
276
+ value: String(j?.approvedCount ?? 0),
224
277
  description: "expenses approved"
225
278
  }),
226
- /* @__PURE__ */ p(n, {
279
+ /* @__PURE__ */ p(r, {
227
280
  title: "Average",
228
- value: t(F?.averageAmount ?? 0),
229
- icon: /* @__PURE__ */ p(d, { className: "h-5 w-5" }),
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
- P.length === 0 && !S ? /* @__PURE__ */ p(ce, {
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: P.map((e) => {
244
- let n = ke === e.id, r = e.status === o.Pending, i = e.status === o.Rejected;
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(d, { className: "h-5 w-5 shrink-0 text-text-secondary" }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("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
- ee(e.date)
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(le, {
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
- r && /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
331
+ i && /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
279
332
  type: "button",
280
- disabled: n,
281
- onClick: () => Pe(e),
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(l, { className: "h-4 w-4" })
337
+ children: /* @__PURE__ */ p(de, { className: "h-4 w-4" })
285
338
  }), /* @__PURE__ */ p("button", {
286
339
  type: "button",
287
- disabled: n,
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(ge, { className: "h-4 w-4" })
346
+ children: /* @__PURE__ */ p(be, { className: "h-4 w-4" })
294
347
  })] }),
295
- i && /* @__PURE__ */ p("button", {
348
+ a && /* @__PURE__ */ p("button", {
296
349
  type: "button",
297
- disabled: n,
298
- onClick: () => Ne(e),
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: () => je(e),
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(fe, { className: "h-4 w-4" })
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(he, { className: "h-4 w-4" })
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
- }), Ee && P.length >= h && /* @__PURE__ */ p("div", {
384
+ }), ze && A.length >= _ && /* @__PURE__ */ p("div", {
325
385
  className: "mt-4 flex justify-center",
326
386
  children: /* @__PURE__ */ p("button", {
327
- onClick: Ie,
328
- disabled: I,
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: I ? "Loading..." : "Load more"
390
+ children: M ? "Loading..." : "Load more"
331
391
  })
332
392
  })] }),
333
- /* @__PURE__ */ p(s, {
334
- open: Oe,
335
- 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.",
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: Z,
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: Q,
344
- disabled: O || A || !V.amount || Number.isNaN(Number(V.amount)) || !V.category.trim(),
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: O || A ? "Saving..." : z ? "Save Changes" : "Add Expense"
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: V.amount,
362
- onChange: (e) => H({
363
- ...V,
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: V.category,
372
- onChange: (e) => H({
373
- ...V,
431
+ value: L.category,
432
+ onChange: (e) => R({
433
+ ...L,
374
434
  category: e.target.value
375
435
  }),
376
- className: _,
377
- children: g.map((e) => /* @__PURE__ */ p("option", {
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: V.description,
390
- onChange: (e) => H({
391
- ...V,
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: V.projectId,
405
- onChange: (e) => H({
406
- ...V,
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: V.date,
416
- onChange: (e) => H({
417
- ...V,
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(s, {
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: Me,
438
- disabled: M,
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: M ? "Deleting..." : "Delete"
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(s, {
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: Fe,
459
- disabled: N,
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: N ? "Rejecting..." : "Reject Expense"
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(ue, {
475
- toasts: _e,
476
- onDismiss: ve
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 { b as ExpenseTrackingPage };
585
+ export { S as ExpenseTrackingPage };
483
586
 
484
587
  //# sourceMappingURL=ExpenseTrackingPage.js.map