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