@pfm-platform/goals-ui-mui 0.1.1

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/index.cjs ADDED
@@ -0,0 +1,876 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var material = require('@mui/material');
5
+ var goalsDataAccess = require('@pfm-platform/goals-data-access');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+ var DeleteIcon = require('@mui/icons-material/Delete');
8
+ var goalsFeature = require('@pfm-platform/goals-feature');
9
+ var iconsMaterial = require('@mui/icons-material');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var DeleteIcon__default = /*#__PURE__*/_interopDefault(DeleteIcon);
14
+
15
+ // src/components/GoalCreateForm.tsx
16
+ function GoalCreateForm({
17
+ userId,
18
+ onSuccess
19
+ }) {
20
+ const [goalType, setGoalType] = react.useState("savings");
21
+ const [name, setName] = react.useState("");
22
+ const [imageName, setImageName] = react.useState("");
23
+ const [targetValue, setTargetValue] = react.useState("");
24
+ const [targetCompletionOn, setTargetCompletionOn] = react.useState("");
25
+ const [error, setError] = react.useState(null);
26
+ const createSavingsGoal = goalsDataAccess.useCreateSavingsGoal({
27
+ onSuccess: () => {
28
+ resetForm();
29
+ onSuccess?.();
30
+ },
31
+ onError: (err) => {
32
+ setError(err instanceof Error ? err.message : "Failed to create savings goal");
33
+ }
34
+ });
35
+ const createPayoffGoal = goalsDataAccess.useCreatePayoffGoal({
36
+ onSuccess: () => {
37
+ resetForm();
38
+ onSuccess?.();
39
+ },
40
+ onError: (err) => {
41
+ setError(err instanceof Error ? err.message : "Failed to create payoff goal");
42
+ }
43
+ });
44
+ const resetForm = () => {
45
+ setName("");
46
+ setImageName("");
47
+ setTargetValue("");
48
+ setTargetCompletionOn("");
49
+ setError(null);
50
+ };
51
+ const handleGoalTypeChange = (event) => {
52
+ setGoalType(event.target.value);
53
+ setError(null);
54
+ };
55
+ const handleSubmit = (e) => {
56
+ e.preventDefault();
57
+ if (!name.trim()) {
58
+ setError("Goal name is required");
59
+ return;
60
+ }
61
+ if (!imageName.trim()) {
62
+ setError("Image name is required");
63
+ return;
64
+ }
65
+ const amount = parseFloat(targetValue);
66
+ if (isNaN(amount) || amount <= 0) {
67
+ setError("Target value must be a positive number");
68
+ return;
69
+ }
70
+ const formattedAmount = amount.toFixed(2);
71
+ const data = {
72
+ name: name.trim(),
73
+ image_name: imageName.trim(),
74
+ target_value: formattedAmount,
75
+ target_completion_on: targetCompletionOn || void 0
76
+ };
77
+ if (goalType === "savings") {
78
+ createSavingsGoal.mutate({ userId, data });
79
+ } else {
80
+ createPayoffGoal.mutate({ userId, data });
81
+ }
82
+ };
83
+ const isPending = createSavingsGoal.isPending || createPayoffGoal.isPending;
84
+ return /* @__PURE__ */ jsxRuntime.jsxs(material.Paper, { sx: { p: 3 }, children: [
85
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "h6", gutterBottom: true, children: "Create Goal" }),
86
+ /* @__PURE__ */ jsxRuntime.jsxs(
87
+ material.Box,
88
+ {
89
+ component: "form",
90
+ onSubmit: handleSubmit,
91
+ sx: { display: "flex", flexDirection: "column", gap: 2 },
92
+ children: [
93
+ /* @__PURE__ */ jsxRuntime.jsxs(material.FormControl, { fullWidth: true, disabled: isPending, children: [
94
+ /* @__PURE__ */ jsxRuntime.jsx(material.InputLabel, { id: "goal-type-label", children: "Goal Type" }),
95
+ /* @__PURE__ */ jsxRuntime.jsxs(
96
+ material.Select,
97
+ {
98
+ labelId: "goal-type-label",
99
+ id: "goal-type",
100
+ value: goalType,
101
+ label: "Goal Type",
102
+ onChange: handleGoalTypeChange,
103
+ children: [
104
+ /* @__PURE__ */ jsxRuntime.jsx(material.MenuItem, { value: "savings", children: "Savings Goal (Save TO a target)" }),
105
+ /* @__PURE__ */ jsxRuntime.jsx(material.MenuItem, { value: "payoff", children: "Payoff Goal (Pay DOWN debt)" })
106
+ ]
107
+ }
108
+ )
109
+ ] }),
110
+ /* @__PURE__ */ jsxRuntime.jsx(
111
+ material.TextField,
112
+ {
113
+ label: "Goal Name",
114
+ value: name,
115
+ onChange: (e) => {
116
+ setName(e.target.value);
117
+ setError(null);
118
+ },
119
+ required: true,
120
+ fullWidth: true,
121
+ disabled: isPending,
122
+ error: !!error && !name.trim(),
123
+ helperText: goalType === "savings" ? "Enter a name for your savings goal (e.g., 'Emergency Fund', 'Vacation')" : "Enter a name for your payoff goal (e.g., 'Credit Card Debt', 'Student Loan')",
124
+ inputProps: {
125
+ maxLength: 255
126
+ }
127
+ }
128
+ ),
129
+ /* @__PURE__ */ jsxRuntime.jsx(
130
+ material.TextField,
131
+ {
132
+ label: "Image Name",
133
+ value: imageName,
134
+ onChange: (e) => {
135
+ setImageName(e.target.value);
136
+ setError(null);
137
+ },
138
+ required: true,
139
+ fullWidth: true,
140
+ disabled: isPending,
141
+ error: !!error && !imageName.trim(),
142
+ helperText: goalType === "savings" ? "Enter image name (e.g., 'piggy_bank', 'vacation', 'house')" : "Enter image name (e.g., 'credit_card', 'loan', 'debt')"
143
+ }
144
+ ),
145
+ /* @__PURE__ */ jsxRuntime.jsx(
146
+ material.TextField,
147
+ {
148
+ label: goalType === "savings" ? "Target Amount" : "Debt Amount",
149
+ type: "number",
150
+ value: targetValue,
151
+ onChange: (e) => {
152
+ setTargetValue(e.target.value);
153
+ setError(null);
154
+ },
155
+ required: true,
156
+ fullWidth: true,
157
+ disabled: isPending,
158
+ error: !!error && (isNaN(parseFloat(targetValue)) || parseFloat(targetValue) <= 0),
159
+ helperText: goalType === "savings" ? "Enter the target amount to save (e.g., 5000.00)" : "Enter the total debt amount to pay off (e.g., 3000.00)",
160
+ inputProps: {
161
+ min: 0.01,
162
+ step: 0.01
163
+ }
164
+ }
165
+ ),
166
+ /* @__PURE__ */ jsxRuntime.jsx(
167
+ material.TextField,
168
+ {
169
+ label: "Target Completion Date (optional)",
170
+ type: "date",
171
+ value: targetCompletionOn,
172
+ onChange: (e) => {
173
+ setTargetCompletionOn(e.target.value);
174
+ setError(null);
175
+ },
176
+ fullWidth: true,
177
+ disabled: isPending,
178
+ helperText: "Enter the date you want to complete this goal by (YYYY-MM-DD)",
179
+ InputLabelProps: {
180
+ shrink: true
181
+ }
182
+ }
183
+ ),
184
+ error && /* @__PURE__ */ jsxRuntime.jsx(material.Alert, { severity: "error", onClose: () => setError(null), children: error }),
185
+ /* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { display: "flex", gap: 1 }, children: /* @__PURE__ */ jsxRuntime.jsx(
186
+ material.Button,
187
+ {
188
+ type: "submit",
189
+ variant: "contained",
190
+ disabled: isPending || !name.trim() || !imageName.trim() || !targetValue,
191
+ startIcon: isPending ? /* @__PURE__ */ jsxRuntime.jsx(material.CircularProgress, { size: 20 }) : null,
192
+ children: isPending ? "Creating..." : `Create ${goalType === "savings" ? "Savings" : "Payoff"} Goal`
193
+ }
194
+ ) })
195
+ ]
196
+ }
197
+ )
198
+ ] });
199
+ }
200
+ function GoalDeleteButton({
201
+ userId,
202
+ goalId,
203
+ goalName,
204
+ goalType
205
+ }) {
206
+ const [open, setOpen] = react.useState(false);
207
+ const deleteSavingsGoal = goalsDataAccess.useDeleteSavingsGoal({
208
+ onSuccess: () => {
209
+ setOpen(false);
210
+ }
211
+ });
212
+ const deletePayoffGoal = goalsDataAccess.useDeletePayoffGoal({
213
+ onSuccess: () => {
214
+ setOpen(false);
215
+ }
216
+ });
217
+ const handleDelete = () => {
218
+ if (goalType === "savings") {
219
+ deleteSavingsGoal.mutate({ userId, goalId });
220
+ } else {
221
+ deletePayoffGoal.mutate({ userId, goalId });
222
+ }
223
+ };
224
+ const isPending = deleteSavingsGoal.isPending || deletePayoffGoal.isPending;
225
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
226
+ /* @__PURE__ */ jsxRuntime.jsx(material.Tooltip, { title: "Delete goal", children: /* @__PURE__ */ jsxRuntime.jsx(
227
+ material.IconButton,
228
+ {
229
+ onClick: () => setOpen(true),
230
+ color: "error",
231
+ size: "small",
232
+ "aria-label": `delete ${goalType} goal ${goalName}`,
233
+ children: /* @__PURE__ */ jsxRuntime.jsx(DeleteIcon__default.default, {})
234
+ }
235
+ ) }),
236
+ /* @__PURE__ */ jsxRuntime.jsxs(
237
+ material.Dialog,
238
+ {
239
+ open,
240
+ onClose: () => !isPending && setOpen(false),
241
+ "aria-labelledby": "delete-goal-dialog-title",
242
+ children: [
243
+ /* @__PURE__ */ jsxRuntime.jsxs(material.DialogTitle, { id: "delete-goal-dialog-title", children: [
244
+ "Delete ",
245
+ goalType === "savings" ? "Savings" : "Payoff",
246
+ " Goal?"
247
+ ] }),
248
+ /* @__PURE__ */ jsxRuntime.jsx(material.DialogContent, { children: /* @__PURE__ */ jsxRuntime.jsxs(material.DialogContentText, { children: [
249
+ "Are you sure you want to delete the ",
250
+ goalType,
251
+ ' goal "',
252
+ goalName,
253
+ '"? This action cannot be undone.'
254
+ ] }) }),
255
+ /* @__PURE__ */ jsxRuntime.jsxs(material.DialogActions, { children: [
256
+ /* @__PURE__ */ jsxRuntime.jsx(
257
+ material.Button,
258
+ {
259
+ onClick: () => setOpen(false),
260
+ disabled: isPending,
261
+ children: "Cancel"
262
+ }
263
+ ),
264
+ /* @__PURE__ */ jsxRuntime.jsx(
265
+ material.Button,
266
+ {
267
+ onClick: handleDelete,
268
+ color: "error",
269
+ variant: "contained",
270
+ disabled: isPending,
271
+ autoFocus: true,
272
+ children: isPending ? "Deleting..." : "Delete"
273
+ }
274
+ )
275
+ ] })
276
+ ]
277
+ }
278
+ )
279
+ ] });
280
+ }
281
+ function GoalEditForm({
282
+ userId,
283
+ goal,
284
+ goalType,
285
+ open,
286
+ onClose
287
+ }) {
288
+ const [name, setName] = react.useState(goal.name);
289
+ const [imageName, setImageName] = react.useState(goal.image_name);
290
+ const [targetValue, setTargetValue] = react.useState(goal.target_value);
291
+ const [targetCompletionOn, setTargetCompletionOn] = react.useState(
292
+ goal.target_completion_on || ""
293
+ );
294
+ const [error, setError] = react.useState(null);
295
+ const updateSavingsGoal = goalsDataAccess.useUpdateSavingsGoal({
296
+ onSuccess: () => {
297
+ setError(null);
298
+ onClose();
299
+ },
300
+ onError: (err) => {
301
+ setError(err instanceof Error ? err.message : "Failed to update savings goal");
302
+ }
303
+ });
304
+ const updatePayoffGoal = goalsDataAccess.useUpdatePayoffGoal({
305
+ onSuccess: () => {
306
+ setError(null);
307
+ onClose();
308
+ },
309
+ onError: (err) => {
310
+ setError(err instanceof Error ? err.message : "Failed to update payoff goal");
311
+ }
312
+ });
313
+ const handleSubmit = (e) => {
314
+ e.preventDefault();
315
+ if (!name.trim()) {
316
+ setError("Goal name is required");
317
+ return;
318
+ }
319
+ if (!imageName.trim()) {
320
+ setError("Image name is required");
321
+ return;
322
+ }
323
+ const amount = parseFloat(targetValue);
324
+ if (isNaN(amount) || amount <= 0) {
325
+ setError("Target value must be a positive number");
326
+ return;
327
+ }
328
+ const formattedAmount = amount.toFixed(2);
329
+ const data = {
330
+ id: goal.id,
331
+ name: name.trim(),
332
+ image_name: imageName.trim(),
333
+ target_value: formattedAmount,
334
+ target_completion_on: targetCompletionOn || void 0
335
+ };
336
+ if (goalType === "savings") {
337
+ updateSavingsGoal.mutate({ userId, goalId: goal.id, data });
338
+ } else {
339
+ updatePayoffGoal.mutate({ userId, goalId: goal.id, data });
340
+ }
341
+ };
342
+ const isPending = updateSavingsGoal.isPending || updatePayoffGoal.isPending;
343
+ const handleClose = () => {
344
+ if (!isPending) {
345
+ onClose();
346
+ }
347
+ };
348
+ return /* @__PURE__ */ jsxRuntime.jsx(
349
+ material.Dialog,
350
+ {
351
+ open,
352
+ onClose: handleClose,
353
+ maxWidth: "sm",
354
+ fullWidth: true,
355
+ "aria-labelledby": "edit-goal-dialog-title",
356
+ children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, children: [
357
+ /* @__PURE__ */ jsxRuntime.jsxs(material.DialogTitle, { id: "edit-goal-dialog-title", children: [
358
+ "Edit ",
359
+ goalType === "savings" ? "Savings" : "Payoff",
360
+ " Goal"
361
+ ] }),
362
+ /* @__PURE__ */ jsxRuntime.jsx(material.DialogContent, { children: /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { display: "flex", flexDirection: "column", gap: 2, pt: 1 }, children: [
363
+ /* @__PURE__ */ jsxRuntime.jsx(
364
+ material.TextField,
365
+ {
366
+ label: "Goal Name",
367
+ value: name,
368
+ onChange: (e) => {
369
+ setName(e.target.value);
370
+ setError(null);
371
+ },
372
+ required: true,
373
+ fullWidth: true,
374
+ disabled: isPending,
375
+ error: !!error && !name.trim(),
376
+ helperText: "Enter a name for your goal",
377
+ inputProps: {
378
+ maxLength: 255
379
+ }
380
+ }
381
+ ),
382
+ /* @__PURE__ */ jsxRuntime.jsx(
383
+ material.TextField,
384
+ {
385
+ label: "Image Name",
386
+ value: imageName,
387
+ onChange: (e) => {
388
+ setImageName(e.target.value);
389
+ setError(null);
390
+ },
391
+ required: true,
392
+ fullWidth: true,
393
+ disabled: isPending,
394
+ error: !!error && !imageName.trim(),
395
+ helperText: "Enter image name"
396
+ }
397
+ ),
398
+ /* @__PURE__ */ jsxRuntime.jsx(
399
+ material.TextField,
400
+ {
401
+ label: goalType === "savings" ? "Target Amount" : "Debt Amount",
402
+ type: "number",
403
+ value: targetValue,
404
+ onChange: (e) => {
405
+ setTargetValue(e.target.value);
406
+ setError(null);
407
+ },
408
+ required: true,
409
+ fullWidth: true,
410
+ disabled: isPending,
411
+ error: !!error && (isNaN(parseFloat(targetValue)) || parseFloat(targetValue) <= 0),
412
+ helperText: "Enter the target amount",
413
+ inputProps: {
414
+ min: 0.01,
415
+ step: 0.01
416
+ }
417
+ }
418
+ ),
419
+ /* @__PURE__ */ jsxRuntime.jsx(
420
+ material.TextField,
421
+ {
422
+ label: "Target Completion Date (optional)",
423
+ type: "date",
424
+ value: targetCompletionOn,
425
+ onChange: (e) => {
426
+ setTargetCompletionOn(e.target.value);
427
+ setError(null);
428
+ },
429
+ fullWidth: true,
430
+ disabled: isPending,
431
+ helperText: "Enter the target completion date (YYYY-MM-DD)",
432
+ InputLabelProps: {
433
+ shrink: true
434
+ }
435
+ }
436
+ ),
437
+ error && /* @__PURE__ */ jsxRuntime.jsx(material.Alert, { severity: "error", onClose: () => setError(null), children: error })
438
+ ] }) }),
439
+ /* @__PURE__ */ jsxRuntime.jsxs(material.DialogActions, { children: [
440
+ /* @__PURE__ */ jsxRuntime.jsx(material.Button, { onClick: handleClose, disabled: isPending, children: "Cancel" }),
441
+ /* @__PURE__ */ jsxRuntime.jsx(
442
+ material.Button,
443
+ {
444
+ type: "submit",
445
+ variant: "contained",
446
+ disabled: isPending || !name.trim() || !imageName.trim() || !targetValue,
447
+ startIcon: isPending ? /* @__PURE__ */ jsxRuntime.jsx(material.CircularProgress, { size: 20 }) : null,
448
+ children: isPending ? "Saving..." : "Save Changes"
449
+ }
450
+ )
451
+ ] })
452
+ ] })
453
+ }
454
+ );
455
+ }
456
+ function GoalList({
457
+ userId,
458
+ type,
459
+ title = "Goals",
460
+ currencySymbol = "$",
461
+ maxItems
462
+ }) {
463
+ const goals = goalsFeature.useGoalProgress({ userId, type });
464
+ if (!goals) {
465
+ return /* @__PURE__ */ jsxRuntime.jsx(material.Card, { children: /* @__PURE__ */ jsxRuntime.jsxs(material.CardContent, { children: [
466
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "h6", gutterBottom: true, children: title }),
467
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "text.secondary", children: "Loading goals..." })
468
+ ] }) });
469
+ }
470
+ const displayGoals = maxItems ? goals.slice(0, maxItems) : goals;
471
+ return /* @__PURE__ */ jsxRuntime.jsx(material.Card, { children: /* @__PURE__ */ jsxRuntime.jsxs(material.CardContent, { children: [
472
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "h6", gutterBottom: true, children: title }),
473
+ displayGoals.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "text.secondary", children: "No goals found" }) : /* @__PURE__ */ jsxRuntime.jsx(material.List, { disablePadding: true, children: displayGoals.map((goal, index) => /* @__PURE__ */ jsxRuntime.jsxs(
474
+ material.ListItem,
475
+ {
476
+ divider: index < displayGoals.length - 1,
477
+ sx: { px: 0, display: "flex", flexDirection: "column", alignItems: "flex-start" },
478
+ children: [
479
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { width: "100%", display: "flex", alignItems: "center", justifyContent: "space-between", mb: 0.5 }, children: [
480
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
481
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", fontWeight: "medium", children: goal.name }),
482
+ /* @__PURE__ */ jsxRuntime.jsx(
483
+ material.Chip,
484
+ {
485
+ label: goal.type,
486
+ size: "small",
487
+ color: goal.type === "savings" ? "success" : "warning",
488
+ variant: "outlined"
489
+ }
490
+ )
491
+ ] }),
492
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "body2", fontWeight: "medium", children: [
493
+ currencySymbol,
494
+ goal.currentValue.toFixed(2),
495
+ " / ",
496
+ currencySymbol,
497
+ goal.targetValue.toFixed(2)
498
+ ] })
499
+ ] }),
500
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { width: "100%" }, children: [
501
+ /* @__PURE__ */ jsxRuntime.jsx(
502
+ material.LinearProgress,
503
+ {
504
+ variant: "determinate",
505
+ value: Math.min(goal.percentComplete, 100),
506
+ color: goal.percentComplete >= 100 ? "success" : "primary",
507
+ sx: { height: 8, borderRadius: 1 }
508
+ }
509
+ ),
510
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "caption", color: "text.secondary", children: [
511
+ goal.percentComplete.toFixed(1),
512
+ "% \u2022 ",
513
+ currencySymbol,
514
+ goal.remaining.toFixed(2),
515
+ " remaining \u2022 ",
516
+ currencySymbol,
517
+ goal.monthlyContribution.toFixed(2),
518
+ "/month"
519
+ ] })
520
+ ] })
521
+ ]
522
+ },
523
+ goal.id
524
+ )) })
525
+ ] }) });
526
+ }
527
+ function GoalSummary({
528
+ userId,
529
+ type,
530
+ title = "Goal Summary",
531
+ currencySymbol = "$"
532
+ }) {
533
+ const goals = goalsFeature.useGoalProgress({ userId, type });
534
+ if (!goals) {
535
+ return /* @__PURE__ */ jsxRuntime.jsx(material.Card, { children: /* @__PURE__ */ jsxRuntime.jsxs(material.CardContent, { children: [
536
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "h6", gutterBottom: true, children: title }),
537
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "text.secondary", children: "Loading goal summary..." })
538
+ ] }) });
539
+ }
540
+ if (goals.length === 0) {
541
+ return /* @__PURE__ */ jsxRuntime.jsx(material.Card, { children: /* @__PURE__ */ jsxRuntime.jsxs(material.CardContent, { children: [
542
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "h6", gutterBottom: true, children: title }),
543
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "text.secondary", children: "No goals found" })
544
+ ] }) });
545
+ }
546
+ const totalGoals = goals.length;
547
+ const totalCurrent = goals.reduce((sum, goal) => sum + goal.currentValue, 0);
548
+ const totalTarget = goals.reduce((sum, goal) => sum + goal.targetValue, 0);
549
+ const totalRemaining = totalTarget - totalCurrent;
550
+ const percentComplete = totalTarget > 0 ? totalCurrent / totalTarget * 100 : 0;
551
+ const totalMonthlyContribution = goals.reduce((sum, goal) => sum + goal.monthlyContribution, 0);
552
+ return /* @__PURE__ */ jsxRuntime.jsx(material.Card, { children: /* @__PURE__ */ jsxRuntime.jsxs(material.CardContent, { children: [
553
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "h6", gutterBottom: true, children: title }),
554
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Grid, { container: true, spacing: 2, children: [
555
+ /* @__PURE__ */ jsxRuntime.jsx(material.Grid, { size: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
556
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "text.secondary", gutterBottom: true, children: "Total Goals" }),
557
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "h5", children: totalGoals })
558
+ ] }) }),
559
+ /* @__PURE__ */ jsxRuntime.jsx(material.Grid, { size: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
560
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "text.secondary", gutterBottom: true, children: "Target Amount" }),
561
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "h5", children: [
562
+ currencySymbol,
563
+ totalTarget.toFixed(2)
564
+ ] })
565
+ ] }) }),
566
+ /* @__PURE__ */ jsxRuntime.jsx(material.Grid, { size: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
567
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "text.secondary", gutterBottom: true, children: "Current Amount" }),
568
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "h6", color: "primary.main", children: [
569
+ currencySymbol,
570
+ totalCurrent.toFixed(2)
571
+ ] })
572
+ ] }) }),
573
+ /* @__PURE__ */ jsxRuntime.jsx(material.Grid, { size: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
574
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "text.secondary", gutterBottom: true, children: "Remaining" }),
575
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "h6", children: [
576
+ currencySymbol,
577
+ totalRemaining.toFixed(2)
578
+ ] })
579
+ ] }) }),
580
+ /* @__PURE__ */ jsxRuntime.jsx(material.Grid, { size: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
581
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "text.secondary", gutterBottom: true, children: "Monthly Contribution" }),
582
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "h6", color: "success.main", children: [
583
+ currencySymbol,
584
+ totalMonthlyContribution.toFixed(2),
585
+ "/month"
586
+ ] })
587
+ ] }) }),
588
+ /* @__PURE__ */ jsxRuntime.jsx(material.Grid, { size: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
589
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { display: "flex", justifyContent: "space-between", mb: 0.5 }, children: [
590
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "text.secondary", children: "Overall Progress" }),
591
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "body2", fontWeight: "medium", children: [
592
+ percentComplete.toFixed(1),
593
+ "%"
594
+ ] })
595
+ ] }),
596
+ /* @__PURE__ */ jsxRuntime.jsx(
597
+ material.LinearProgress,
598
+ {
599
+ variant: "determinate",
600
+ value: Math.min(percentComplete, 100),
601
+ color: percentComplete >= 100 ? "success" : "primary",
602
+ sx: { height: 10, borderRadius: 1 }
603
+ }
604
+ )
605
+ ] }) })
606
+ ] })
607
+ ] }) });
608
+ }
609
+ function GoalProgressCard({
610
+ name,
611
+ currentValue,
612
+ targetValue,
613
+ initialValue: _initialValue,
614
+ percentComplete,
615
+ goalType,
616
+ imageUrl,
617
+ currencySymbol = "$",
618
+ complete = false,
619
+ monthsRemaining,
620
+ monthlyContribution
621
+ }) {
622
+ const current = parseFloat(currentValue);
623
+ const target = parseFloat(targetValue);
624
+ const remaining = goalType === "savings" ? target - current : current;
625
+ const progressColor = complete ? "success" : percentComplete >= 75 ? "primary" : percentComplete >= 50 ? "info" : percentComplete >= 25 ? "warning" : "error";
626
+ const formatCurrency = (amount) => {
627
+ return `${currencySymbol}${amount.toFixed(2)}`;
628
+ };
629
+ return /* @__PURE__ */ jsxRuntime.jsx(material.Card, { children: /* @__PURE__ */ jsxRuntime.jsxs(material.CardContent, { children: [
630
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { display: "flex", alignItems: "flex-start", gap: 2, mb: 2, children: [
631
+ imageUrl && /* @__PURE__ */ jsxRuntime.jsx(
632
+ material.Avatar,
633
+ {
634
+ src: imageUrl,
635
+ alt: name,
636
+ sx: { width: 56, height: 56 },
637
+ variant: "rounded"
638
+ }
639
+ ),
640
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { flex: 1, children: [
641
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "h6", gutterBottom: true, children: name }),
642
+ /* @__PURE__ */ jsxRuntime.jsx(
643
+ material.Chip,
644
+ {
645
+ label: goalType === "savings" ? "Savings Goal" : "Payoff Goal",
646
+ size: "small",
647
+ color: goalType === "savings" ? "success" : "warning",
648
+ icon: goalType === "savings" ? /* @__PURE__ */ jsxRuntime.jsx(iconsMaterial.TrendingUp, {}) : /* @__PURE__ */ jsxRuntime.jsx(iconsMaterial.Flag, {})
649
+ }
650
+ )
651
+ ] }),
652
+ complete && /* @__PURE__ */ jsxRuntime.jsx(iconsMaterial.EmojiEvents, { color: "success", sx: { fontSize: 40 } })
653
+ ] }),
654
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { mb: 2 }, children: [
655
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { display: "flex", justifyContent: "space-between", mb: 1, children: [
656
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "text.secondary", children: "Progress" }),
657
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "body2", fontWeight: "bold", color: `${progressColor}.main`, children: [
658
+ percentComplete,
659
+ "%"
660
+ ] })
661
+ ] }),
662
+ /* @__PURE__ */ jsxRuntime.jsx(
663
+ material.LinearProgress,
664
+ {
665
+ variant: "determinate",
666
+ value: Math.min(percentComplete, 100),
667
+ color: progressColor,
668
+ sx: { height: 8, borderRadius: 4 }
669
+ }
670
+ )
671
+ ] }),
672
+ /* @__PURE__ */ jsxRuntime.jsxs(
673
+ material.Box,
674
+ {
675
+ display: "grid",
676
+ gridTemplateColumns: "1fr 1fr",
677
+ gap: 2,
678
+ sx: { mb: 2 },
679
+ children: [
680
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
681
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "caption", color: "text.secondary", children: goalType === "savings" ? "Current" : "Remaining" }),
682
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "h6", color: "text.primary", children: formatCurrency(goalType === "savings" ? current : remaining) })
683
+ ] }),
684
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
685
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "caption", color: "text.secondary", children: "Target" }),
686
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "h6", color: "text.primary", children: formatCurrency(target) })
687
+ ] })
688
+ ]
689
+ }
690
+ ),
691
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { display: "flex", flexDirection: "column", gap: 1, children: [
692
+ monthlyContribution && parseFloat(monthlyContribution) > 0 && /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { display: "flex", alignItems: "center", gap: 1, children: [
693
+ /* @__PURE__ */ jsxRuntime.jsx(iconsMaterial.TrendingUp, { fontSize: "small", color: "action" }),
694
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "caption", color: "text.secondary", children: [
695
+ formatCurrency(parseFloat(monthlyContribution)),
696
+ "/month"
697
+ ] })
698
+ ] }),
699
+ monthsRemaining !== void 0 && monthsRemaining > 0 && !complete && /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { display: "flex", alignItems: "center", gap: 1, children: [
700
+ /* @__PURE__ */ jsxRuntime.jsx(iconsMaterial.Timer, { fontSize: "small", color: "action" }),
701
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "caption", color: "text.secondary", children: [
702
+ "~",
703
+ monthsRemaining,
704
+ " ",
705
+ monthsRemaining === 1 ? "month" : "months",
706
+ " remaining"
707
+ ] })
708
+ ] }),
709
+ complete && /* @__PURE__ */ jsxRuntime.jsx(
710
+ material.Chip,
711
+ {
712
+ label: "Goal Achieved!",
713
+ color: "success",
714
+ size: "small",
715
+ icon: /* @__PURE__ */ jsxRuntime.jsx(iconsMaterial.EmojiEvents, {}),
716
+ sx: { mt: 1 }
717
+ }
718
+ ),
719
+ !complete && remaining > 0 && /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "caption", color: "text.secondary", children: [
720
+ formatCurrency(remaining),
721
+ " to go"
722
+ ] })
723
+ ] })
724
+ ] }) });
725
+ }
726
+ function GoalMilestoneTracker({
727
+ goalName,
728
+ currentPercent,
729
+ milestones,
730
+ showProgressBar = true
731
+ }) {
732
+ const defaultMilestones = [
733
+ {
734
+ name: "25% Complete",
735
+ targetPercent: 25,
736
+ achieved: currentPercent >= 25,
737
+ description: "Great start!"
738
+ },
739
+ {
740
+ name: "50% Complete",
741
+ targetPercent: 50,
742
+ achieved: currentPercent >= 50,
743
+ description: "Halfway there!"
744
+ },
745
+ {
746
+ name: "75% Complete",
747
+ targetPercent: 75,
748
+ achieved: currentPercent >= 75,
749
+ description: "Almost done!"
750
+ },
751
+ {
752
+ name: "Goal Achieved",
753
+ targetPercent: 100,
754
+ achieved: currentPercent >= 100,
755
+ description: "Congratulations!",
756
+ reward: "\u{1F389}"
757
+ }
758
+ ];
759
+ const activeMilestones = milestones || defaultMilestones;
760
+ const achievedCount = activeMilestones.filter((m) => m.achieved).length;
761
+ const currentMilestoneIndex = activeMilestones.findIndex((m) => !m.achieved);
762
+ const nextMilestone = currentMilestoneIndex >= 0 ? activeMilestones[currentMilestoneIndex] : null;
763
+ return /* @__PURE__ */ jsxRuntime.jsx(material.Card, { children: /* @__PURE__ */ jsxRuntime.jsxs(material.CardContent, { children: [
764
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { display: "flex", alignItems: "center", justifyContent: "space-between", mb: 2, children: [
765
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "h6", children: "Milestones" }),
766
+ /* @__PURE__ */ jsxRuntime.jsx(
767
+ material.Chip,
768
+ {
769
+ label: `${achievedCount}/${activeMilestones.length}`,
770
+ color: achievedCount === activeMilestones.length ? "success" : "primary",
771
+ size: "small"
772
+ }
773
+ )
774
+ ] }),
775
+ showProgressBar && /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { sx: { mb: 3 }, children: [
776
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { display: "flex", justifyContent: "space-between", mb: 1, children: [
777
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "text.secondary", children: goalName }),
778
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "body2", fontWeight: "bold", children: [
779
+ currentPercent,
780
+ "%"
781
+ ] })
782
+ ] }),
783
+ /* @__PURE__ */ jsxRuntime.jsx(
784
+ material.LinearProgress,
785
+ {
786
+ variant: "determinate",
787
+ value: Math.min(currentPercent, 100),
788
+ color: currentPercent >= 100 ? "success" : "primary",
789
+ sx: { height: 8, borderRadius: 4 }
790
+ }
791
+ )
792
+ ] }),
793
+ nextMilestone && /* @__PURE__ */ jsxRuntime.jsxs(
794
+ material.Box,
795
+ {
796
+ sx: {
797
+ p: 2,
798
+ mb: 2,
799
+ bgcolor: "primary.light",
800
+ borderRadius: 1,
801
+ display: "flex",
802
+ alignItems: "center",
803
+ gap: 1
804
+ },
805
+ children: [
806
+ /* @__PURE__ */ jsxRuntime.jsx(iconsMaterial.Stars, { color: "primary" }),
807
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { children: [
808
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "subtitle2", color: "primary.dark", children: [
809
+ "Next Milestone: ",
810
+ nextMilestone.name
811
+ ] }),
812
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "caption", color: "primary.dark", children: [
813
+ nextMilestone.targetPercent - currentPercent,
814
+ "% to go"
815
+ ] })
816
+ ] })
817
+ ]
818
+ }
819
+ ),
820
+ /* @__PURE__ */ jsxRuntime.jsx(material.Stepper, { orientation: "vertical", activeStep: currentMilestoneIndex, children: activeMilestones.map((milestone) => /* @__PURE__ */ jsxRuntime.jsxs(material.Step, { completed: milestone.achieved, children: [
821
+ /* @__PURE__ */ jsxRuntime.jsx(
822
+ material.StepLabel,
823
+ {
824
+ optional: milestone.reward && milestone.achieved ? /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "caption", children: milestone.reward }) : null,
825
+ StepIconComponent: () => milestone.achieved ? /* @__PURE__ */ jsxRuntime.jsx(iconsMaterial.CheckCircle, { color: "success" }) : /* @__PURE__ */ jsxRuntime.jsx(iconsMaterial.RadioButtonUnchecked, { color: "action" }),
826
+ children: /* @__PURE__ */ jsxRuntime.jsxs(material.Box, { display: "flex", alignItems: "center", gap: 1, children: [
827
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", fontWeight: milestone.achieved ? "bold" : "normal", children: milestone.name }),
828
+ /* @__PURE__ */ jsxRuntime.jsxs(material.Typography, { variant: "caption", color: "text.secondary", children: [
829
+ "(",
830
+ milestone.targetPercent,
831
+ "%)"
832
+ ] })
833
+ ] })
834
+ }
835
+ ),
836
+ /* @__PURE__ */ jsxRuntime.jsxs(material.StepContent, { children: [
837
+ milestone.description && /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "caption", color: "text.secondary", paragraph: true, children: milestone.description }),
838
+ milestone.achieved && /* @__PURE__ */ jsxRuntime.jsx(
839
+ material.Chip,
840
+ {
841
+ label: "Achieved",
842
+ color: "success",
843
+ size: "small",
844
+ icon: /* @__PURE__ */ jsxRuntime.jsx(iconsMaterial.CheckCircle, {})
845
+ }
846
+ )
847
+ ] })
848
+ ] }, milestone.name)) }),
849
+ achievedCount === activeMilestones.length && /* @__PURE__ */ jsxRuntime.jsxs(
850
+ material.Box,
851
+ {
852
+ sx: {
853
+ mt: 2,
854
+ p: 2,
855
+ bgcolor: "success.light",
856
+ borderRadius: 1,
857
+ textAlign: "center"
858
+ },
859
+ children: [
860
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "h6", color: "success.dark", gutterBottom: true, children: "\u{1F389} All Milestones Achieved! \u{1F389}" }),
861
+ /* @__PURE__ */ jsxRuntime.jsx(material.Typography, { variant: "body2", color: "success.dark", children: "Congratulations on reaching your goal!" })
862
+ ]
863
+ }
864
+ )
865
+ ] }) });
866
+ }
867
+
868
+ exports.GoalCreateForm = GoalCreateForm;
869
+ exports.GoalDeleteButton = GoalDeleteButton;
870
+ exports.GoalEditForm = GoalEditForm;
871
+ exports.GoalList = GoalList;
872
+ exports.GoalMilestoneTracker = GoalMilestoneTracker;
873
+ exports.GoalProgressCard = GoalProgressCard;
874
+ exports.GoalSummary = GoalSummary;
875
+ //# sourceMappingURL=index.cjs.map
876
+ //# sourceMappingURL=index.cjs.map