@pfm-platform/budgets-ui-mui 0.1.1 → 0.2.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 CHANGED
@@ -1,21 +1,21 @@
1
- import { Card, CardContent, Typography, List, ListItem, Box, Chip, LinearProgress, Grid, Paper, TextField, Alert, Button, CircularProgress, Dialog, DialogTitle, DialogContent, DialogActions, Tooltip, IconButton, DialogContentText, FormControl, InputLabel, Select, MenuItem, useTheme, alpha, Divider } from '@mui/material';
1
+ import { Card, CardContent, Typography, List, ListItem, Box, Chip, LinearProgress, Grid, Paper, TextField, Alert, Button, CircularProgress, Dialog, DialogTitle, DialogContent, DialogActions, Tooltip, IconButton, DialogContentText, FormControl, InputLabel, Select, MenuItem, useTheme, alpha, Divider, Stepper, Step, StepLabel, Autocomplete } from '@mui/material';
2
2
  import { useBudgetProgress, useBudgetSummary } from '@pfm-platform/budgets-feature';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { useState, useMemo, useEffect } from 'react';
4
+ import { useState, useMemo, useEffect, createElement } from 'react';
5
5
  import { useCreateBudget, useUpdateBudget, useDeleteBudget } from '@pfm-platform/budgets-data-access';
6
6
  import DeleteIcon from '@mui/icons-material/Delete';
7
- import { Warning, TrendingUp, TrendingDown, ExpandMore, CheckCircle } from '@mui/icons-material';
7
+ import { Warning, TrendingUp, TrendingDown, ExpandMore, Info, LocalOffer, Add, CheckCircle, Error as Error$1 } from '@mui/icons-material';
8
8
 
9
9
  // src/components/BudgetList.tsx
10
10
  function BudgetList({
11
11
  userId,
12
- start_date,
13
- end_date,
12
+ month,
13
+ year,
14
14
  title = "Budgets",
15
15
  currencySymbol = "$",
16
16
  maxItems
17
17
  }) {
18
- const budgets = useBudgetProgress({ userId, start_date, end_date });
18
+ const budgets = useBudgetProgress({ userId, month, year });
19
19
  if (!budgets) {
20
20
  return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { children: [
21
21
  /* @__PURE__ */ jsx(Typography, { variant: "h6", gutterBottom: true, children: title }),
@@ -78,12 +78,12 @@ function BudgetList({
78
78
  }
79
79
  function BudgetSummary({
80
80
  userId,
81
- start_date,
82
- end_date,
81
+ month,
82
+ year,
83
83
  title = "Budget Summary",
84
84
  currencySymbol = "$"
85
85
  }) {
86
- const summary = useBudgetSummary({ userId, start_date, end_date });
86
+ const summary = useBudgetSummary({ userId, month, year });
87
87
  if (!summary) {
88
88
  return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { children: [
89
89
  /* @__PURE__ */ jsx(Typography, { variant: "h6", gutterBottom: true, children: title }),
@@ -205,13 +205,16 @@ function BudgetCreateForm({
205
205
  setError("At least one tag is required");
206
206
  return;
207
207
  }
208
+ const now = /* @__PURE__ */ new Date();
208
209
  const data = {
210
+ user_id: userId,
209
211
  name: name.trim(),
212
+ month: now.getMonth() + 1,
213
+ year: now.getFullYear(),
210
214
  budget_amount: amount,
211
- tag_names: tags,
212
215
  show_on_dashboard: true
213
216
  };
214
- createBudget.mutate({ userId, data });
217
+ createBudget.mutate({ userId, data, tagNames: tags });
215
218
  };
216
219
  return /* @__PURE__ */ jsxs(Paper, { sx: { p: 3 }, children: [
217
220
  /* @__PURE__ */ jsx(Typography, { variant: "h6", gutterBottom: true, children: "Create Budget" }),
@@ -304,7 +307,7 @@ function BudgetEditForm({
304
307
  }) {
305
308
  const [name, setName] = useState(budget.name);
306
309
  const [budgetAmount, setBudgetAmount] = useState(budget.budget_amount.toString());
307
- const [tagNames, setTagNames] = useState(budget.tag_names.join(", "));
310
+ const [tagNames, setTagNames] = useState(budget.budget_tags.map((bt) => bt.tag_name).join(", "));
308
311
  const [error, setError] = useState(null);
309
312
  const updateBudget = useUpdateBudget({
310
313
  onSuccess: () => {
@@ -335,10 +338,9 @@ function BudgetEditForm({
335
338
  id: budget.id,
336
339
  name: name.trim(),
337
340
  budget_amount: amount,
338
- tag_names: tags,
339
341
  show_on_dashboard: true
340
342
  };
341
- updateBudget.mutate({ userId, budgetId: budget.id, data });
343
+ updateBudget.mutate({ userId, budgetId: budget.id, data, tagNames: tags });
342
344
  };
343
345
  const handleClose = () => {
344
346
  if (!updateBudget.isPending) {
@@ -519,10 +521,10 @@ function BudgetProgressCard({
519
521
  /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", mb: 2 }, children: [
520
522
  /* @__PURE__ */ jsxs(Box, { children: [
521
523
  /* @__PURE__ */ jsx(Typography, { variant: "h6", gutterBottom: true, children: budget.name }),
522
- budget.tag_names.length > 0 && /* @__PURE__ */ jsx(
524
+ budget.budget_tags.length > 0 && /* @__PURE__ */ jsx(
523
525
  Chip,
524
526
  {
525
- label: budget.tag_names[0],
527
+ label: budget.budget_tags[0].tag_name,
526
528
  size: "small",
527
529
  variant: "outlined",
528
530
  sx: { mb: 1 }
@@ -624,8 +626,8 @@ function BudgetProgressList({
624
626
  const categories = useMemo(() => {
625
627
  const uniqueCategories = /* @__PURE__ */ new Set();
626
628
  budgets.forEach((budget) => {
627
- budget.tag_names.forEach((tag) => {
628
- uniqueCategories.add(tag);
629
+ budget.budget_tags.forEach((bt) => {
630
+ uniqueCategories.add(bt.tag_name);
629
631
  });
630
632
  });
631
633
  return Array.from(uniqueCategories).sort();
@@ -638,7 +640,7 @@ function BudgetProgressList({
638
640
  if (statusFilter === "on-track" && percentage > 70) return false;
639
641
  if (statusFilter === "warning" && (percentage <= 70 || percentage > 90)) return false;
640
642
  if (statusFilter === "over-budget" && percentage <= 90) return false;
641
- if (categoryFilter !== "all" && !budget.tag_names.includes(categoryFilter)) return false;
643
+ if (categoryFilter !== "all" && !budget.budget_tags.some((bt) => bt.tag_name === categoryFilter)) return false;
642
644
  return true;
643
645
  });
644
646
  }, [budgets, spentByBudget, statusFilter, categoryFilter]);
@@ -1084,7 +1086,669 @@ function BudgetInsightDialog({
1084
1086
  }
1085
1087
  );
1086
1088
  }
1089
+ function getStateConfig(state, theme) {
1090
+ switch (state) {
1091
+ case "under":
1092
+ return {
1093
+ color: theme.palette.success.main,
1094
+ bgColor: alpha(theme.palette.success.main, 0.1),
1095
+ icon: /* @__PURE__ */ jsx(TrendingDown, {}),
1096
+ label: "Under Budget",
1097
+ chipColor: "success"
1098
+ };
1099
+ case "on-track":
1100
+ return {
1101
+ color: theme.palette.info.main,
1102
+ bgColor: alpha(theme.palette.info.main, 0.1),
1103
+ icon: /* @__PURE__ */ jsx(CheckCircle, {}),
1104
+ label: "On Track",
1105
+ chipColor: "info"
1106
+ };
1107
+ case "over":
1108
+ return {
1109
+ color: theme.palette.warning.main,
1110
+ bgColor: alpha(theme.palette.warning.main, 0.1),
1111
+ icon: /* @__PURE__ */ jsx(Warning, {}),
1112
+ label: "Over Budget",
1113
+ chipColor: "warning"
1114
+ };
1115
+ case "critical":
1116
+ return {
1117
+ color: theme.palette.error.main,
1118
+ bgColor: alpha(theme.palette.error.main, 0.1),
1119
+ icon: /* @__PURE__ */ jsx(Error$1, {}),
1120
+ label: "Critical",
1121
+ chipColor: "error"
1122
+ };
1123
+ }
1124
+ }
1125
+ function getSizeConfig(size) {
1126
+ switch (size) {
1127
+ case "small":
1128
+ return {
1129
+ iconSize: 16,
1130
+ fontSize: "0.75rem",
1131
+ spacing: 0.5
1132
+ };
1133
+ case "medium":
1134
+ return {
1135
+ iconSize: 20,
1136
+ fontSize: "0.875rem",
1137
+ spacing: 1
1138
+ };
1139
+ case "large":
1140
+ return {
1141
+ iconSize: 24,
1142
+ fontSize: "1rem",
1143
+ spacing: 1.5
1144
+ };
1145
+ }
1146
+ }
1147
+ function BudgetStateIndicator({
1148
+ state,
1149
+ percentage,
1150
+ variant = "chip",
1151
+ size = "medium",
1152
+ showPercentage = true,
1153
+ showLabel = true,
1154
+ customLabel
1155
+ }) {
1156
+ const theme = useTheme();
1157
+ const config = getStateConfig(state, theme);
1158
+ const sizeConfig = getSizeConfig(size);
1159
+ const label = customLabel || config.label;
1160
+ const displayText = showPercentage ? `${label} (${percentage.toFixed(0)}%)` : label;
1161
+ if (variant === "chip") {
1162
+ return /* @__PURE__ */ jsx(
1163
+ Chip,
1164
+ {
1165
+ "data-testid": "budget-state-indicator",
1166
+ icon: config.icon,
1167
+ label: displayText,
1168
+ color: config.chipColor,
1169
+ size: size === "large" ? "medium" : "small",
1170
+ sx: {
1171
+ fontSize: sizeConfig.fontSize,
1172
+ fontWeight: "medium"
1173
+ }
1174
+ }
1175
+ );
1176
+ }
1177
+ if (variant === "inline") {
1178
+ return /* @__PURE__ */ jsxs(
1179
+ Box,
1180
+ {
1181
+ "data-testid": "budget-state-indicator",
1182
+ sx: {
1183
+ display: "inline-flex",
1184
+ alignItems: "center",
1185
+ gap: sizeConfig.spacing,
1186
+ color: config.color
1187
+ },
1188
+ children: [
1189
+ /* @__PURE__ */ jsx(
1190
+ Box,
1191
+ {
1192
+ sx: {
1193
+ display: "flex",
1194
+ alignItems: "center",
1195
+ fontSize: sizeConfig.iconSize
1196
+ },
1197
+ children: config.icon
1198
+ }
1199
+ ),
1200
+ showLabel && /* @__PURE__ */ jsx(
1201
+ Typography,
1202
+ {
1203
+ component: "span",
1204
+ sx: {
1205
+ fontSize: sizeConfig.fontSize,
1206
+ fontWeight: "medium"
1207
+ },
1208
+ children: displayText
1209
+ }
1210
+ )
1211
+ ]
1212
+ }
1213
+ );
1214
+ }
1215
+ return /* @__PURE__ */ jsxs(
1216
+ Box,
1217
+ {
1218
+ "data-testid": "budget-state-indicator",
1219
+ sx: {
1220
+ display: "flex",
1221
+ alignItems: "center",
1222
+ gap: sizeConfig.spacing * 2,
1223
+ p: sizeConfig.spacing * 2,
1224
+ bgcolor: config.bgColor,
1225
+ borderRadius: 1,
1226
+ borderLeft: `4px solid ${config.color}`
1227
+ },
1228
+ children: [
1229
+ /* @__PURE__ */ jsx(
1230
+ Box,
1231
+ {
1232
+ sx: {
1233
+ color: config.color,
1234
+ display: "flex",
1235
+ alignItems: "center",
1236
+ fontSize: sizeConfig.iconSize * 1.5
1237
+ },
1238
+ children: config.icon
1239
+ }
1240
+ ),
1241
+ /* @__PURE__ */ jsxs(Box, { sx: { flex: 1 }, children: [
1242
+ showLabel && /* @__PURE__ */ jsx(
1243
+ Typography,
1244
+ {
1245
+ variant: "subtitle2",
1246
+ sx: {
1247
+ color: config.color,
1248
+ fontWeight: "bold",
1249
+ fontSize: sizeConfig.fontSize
1250
+ },
1251
+ children: label
1252
+ }
1253
+ ),
1254
+ showPercentage && /* @__PURE__ */ jsxs(
1255
+ Typography,
1256
+ {
1257
+ variant: "body2",
1258
+ color: "text.secondary",
1259
+ sx: { fontSize: sizeConfig.fontSize },
1260
+ children: [
1261
+ percentage.toFixed(1),
1262
+ "% of budget used"
1263
+ ]
1264
+ }
1265
+ )
1266
+ ] })
1267
+ ]
1268
+ }
1269
+ );
1270
+ }
1271
+ var defaultCurrencyFormatter3 = (value) => {
1272
+ return new Intl.NumberFormat("en-US", {
1273
+ style: "currency",
1274
+ currency: "USD",
1275
+ minimumFractionDigits: 0,
1276
+ maximumFractionDigits: 0
1277
+ }).format(value);
1278
+ };
1279
+ function getTrendConfig(trend, theme) {
1280
+ switch (trend) {
1281
+ case "increasing":
1282
+ return {
1283
+ color: theme.palette.warning.main,
1284
+ bgColor: alpha(theme.palette.warning.main, 0.1),
1285
+ label: "Increasing"
1286
+ };
1287
+ case "decreasing":
1288
+ return {
1289
+ color: theme.palette.success.main,
1290
+ bgColor: alpha(theme.palette.success.main, 0.1),
1291
+ label: "Decreasing"
1292
+ };
1293
+ case "stable":
1294
+ return {
1295
+ color: theme.palette.info.main,
1296
+ bgColor: alpha(theme.palette.info.main, 0.1),
1297
+ label: "Stable"
1298
+ };
1299
+ }
1300
+ }
1301
+ function BudgetTagInsight({
1302
+ insights,
1303
+ loading = false,
1304
+ onCreateBudget,
1305
+ onViewDetails,
1306
+ onDismiss,
1307
+ currencyFormatter = defaultCurrencyFormatter3,
1308
+ maxVisible = 5
1309
+ }) {
1310
+ const theme = useTheme();
1311
+ const visibleInsights = insights.slice(0, maxVisible);
1312
+ const unbudgetedCount = insights.filter((i) => !i.isBudgeted).length;
1313
+ if (loading) {
1314
+ return /* @__PURE__ */ jsx(Card, { "data-testid": "budget-tag-insight", children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { textAlign: "center", py: 2 }, children: "Loading tag insights..." }) }) });
1315
+ }
1316
+ if (insights.length === 0) {
1317
+ return /* @__PURE__ */ jsx(Card, { "data-testid": "budget-tag-insight", children: /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(Box, { sx: { textAlign: "center", py: 3 }, children: [
1318
+ /* @__PURE__ */ jsx(Info, { sx: { fontSize: 48, color: "text.disabled", mb: 1 } }),
1319
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: "No tag insights available yet. Start tracking your expenses!" })
1320
+ ] }) }) });
1321
+ }
1322
+ return /* @__PURE__ */ jsx(Card, { "data-testid": "budget-tag-insight", children: /* @__PURE__ */ jsxs(CardContent, { children: [
1323
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 2 }, children: [
1324
+ /* @__PURE__ */ jsx(LocalOffer, { color: "primary" }),
1325
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", children: "Tag Spending Insights" }),
1326
+ unbudgetedCount > 0 && /* @__PURE__ */ jsx(
1327
+ Chip,
1328
+ {
1329
+ label: `${unbudgetedCount} unbudgeted`,
1330
+ size: "small",
1331
+ color: "warning",
1332
+ sx: { ml: "auto" }
1333
+ }
1334
+ )
1335
+ ] }),
1336
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mb: 2 }, children: "Discover spending patterns and create budgets for frequently used tags." }),
1337
+ /* @__PURE__ */ jsx(Divider, { sx: { mb: 2 } }),
1338
+ visibleInsights.map((insight) => {
1339
+ const trendConfig = getTrendConfig(insight.trend, theme);
1340
+ return /* @__PURE__ */ jsxs(
1341
+ Box,
1342
+ {
1343
+ "data-testid": `tag-insight-${insight.id}`,
1344
+ sx: {
1345
+ p: 2,
1346
+ mb: 2,
1347
+ bgcolor: insight.isBudgeted ? alpha(theme.palette.success.main, 0.05) : alpha(theme.palette.warning.main, 0.05),
1348
+ borderRadius: 1,
1349
+ borderLeft: insight.isBudgeted ? `4px solid ${theme.palette.success.main}` : `4px solid ${theme.palette.warning.main}`
1350
+ },
1351
+ children: [
1352
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1 }, children: [
1353
+ /* @__PURE__ */ jsx(LocalOffer, { fontSize: "small", color: "action" }),
1354
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", sx: { fontWeight: "bold" }, children: insight.tagName }),
1355
+ insight.isBudgeted && /* @__PURE__ */ jsx(Chip, { label: "Budgeted", size: "small", color: "success", variant: "outlined" }),
1356
+ !insight.isBudgeted && /* @__PURE__ */ jsx(Chip, { label: "No Budget", size: "small", color: "warning", variant: "outlined" })
1357
+ ] }),
1358
+ /* @__PURE__ */ jsxs(Box, { sx: { mb: 2 }, children: [
1359
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mb: 0.5 }, children: insight.recommendation }),
1360
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1, mt: 1 }, children: [
1361
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", color: "primary", children: currencyFormatter(insight.averageMonthlySpend) }),
1362
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", children: "avg/month" })
1363
+ ] })
1364
+ ] }),
1365
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", gap: 1, flexWrap: "wrap", mb: 2 }, children: [
1366
+ /* @__PURE__ */ jsx(
1367
+ Chip,
1368
+ {
1369
+ label: `${insight.transactionCount} transactions`,
1370
+ size: "small",
1371
+ variant: "outlined"
1372
+ }
1373
+ ),
1374
+ /* @__PURE__ */ jsx(
1375
+ Chip,
1376
+ {
1377
+ label: `${insight.monthsAnalyzed} months`,
1378
+ size: "small",
1379
+ variant: "outlined"
1380
+ }
1381
+ ),
1382
+ /* @__PURE__ */ jsx(
1383
+ Chip,
1384
+ {
1385
+ icon: /* @__PURE__ */ jsx(TrendingUp, {}),
1386
+ label: `${trendConfig.label} ${Math.abs(insight.trendPercentage).toFixed(0)}%`,
1387
+ size: "small",
1388
+ sx: {
1389
+ bgcolor: trendConfig.bgColor,
1390
+ color: trendConfig.color,
1391
+ borderColor: trendConfig.color
1392
+ },
1393
+ variant: "outlined"
1394
+ }
1395
+ )
1396
+ ] }),
1397
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", gap: 1, justifyContent: "flex-end" }, children: [
1398
+ onDismiss && /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => onDismiss(insight), children: "Dismiss" }),
1399
+ onViewDetails && /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => onViewDetails(insight), children: "View Details" }),
1400
+ !insight.isBudgeted && onCreateBudget && /* @__PURE__ */ jsxs(
1401
+ Button,
1402
+ {
1403
+ size: "small",
1404
+ variant: "contained",
1405
+ startIcon: /* @__PURE__ */ jsx(Add, {}),
1406
+ onClick: () => onCreateBudget(insight),
1407
+ children: [
1408
+ "Create Budget",
1409
+ insight.suggestedBudget && ` (${currencyFormatter(insight.suggestedBudget)})`
1410
+ ]
1411
+ }
1412
+ )
1413
+ ] })
1414
+ ]
1415
+ },
1416
+ insight.id
1417
+ );
1418
+ }),
1419
+ insights.length > maxVisible && /* @__PURE__ */ jsxs(
1420
+ Typography,
1421
+ {
1422
+ variant: "caption",
1423
+ color: "text.secondary",
1424
+ sx: { textAlign: "center", display: "block", mt: 1 },
1425
+ children: [
1426
+ "Showing ",
1427
+ maxVisible,
1428
+ " of ",
1429
+ insights.length,
1430
+ " insights"
1431
+ ]
1432
+ }
1433
+ )
1434
+ ] }) });
1435
+ }
1436
+ var defaultCurrencyFormatter4 = (value) => {
1437
+ return new Intl.NumberFormat("en-US", {
1438
+ style: "currency",
1439
+ currency: "USD",
1440
+ minimumFractionDigits: 0,
1441
+ maximumFractionDigits: 0
1442
+ }).format(Math.abs(value));
1443
+ };
1444
+ function getOverviewMessage(data, currencyFormatter) {
1445
+ const { totalBudgets, budgetsOver, budgetsOnTrack, budgetsUnder, totalOverUnder, unbudgetedTagsCount } = data;
1446
+ if (totalBudgets === 0) {
1447
+ return {
1448
+ type: "info",
1449
+ title: "No Budgets Yet",
1450
+ message: "Create your first budget to start tracking your spending and reach your financial goals.",
1451
+ defaultActionLabel: "Create Budget"
1452
+ };
1453
+ }
1454
+ if (budgetsOver === totalBudgets && totalOverUnder < 0) {
1455
+ return {
1456
+ type: "error",
1457
+ title: "All Budgets Over Limit",
1458
+ message: `You've exceeded all ${totalBudgets} budget${totalBudgets > 1 ? "s" : ""} by ${currencyFormatter(totalOverUnder)} total. Review your spending and adjust budgets as needed.`,
1459
+ defaultActionLabel: "Review Budgets"
1460
+ };
1461
+ }
1462
+ if (budgetsOver > 0) {
1463
+ return {
1464
+ type: "warning",
1465
+ title: `${budgetsOver} Budget${budgetsOver > 1 ? "s" : ""} Over Limit`,
1466
+ message: `You're over budget on ${budgetsOver} of ${totalBudgets} budgets by ${currencyFormatter(totalOverUnder)} total. Consider adjusting these budgets.`,
1467
+ defaultActionLabel: "View Details"
1468
+ };
1469
+ }
1470
+ if (budgetsOnTrack === totalBudgets) {
1471
+ const unbudgetedMsg = unbudgetedTagsCount && unbudgetedTagsCount > 0 ? ` You have ${unbudgetedTagsCount} unbudgeted tag${unbudgetedTagsCount > 1 ? "s" : ""} worth reviewing.` : "";
1472
+ return {
1473
+ type: "success",
1474
+ title: "All Budgets On Track",
1475
+ message: `Great job! All ${totalBudgets} budget${totalBudgets > 1 ? "s are" : " is"} on track.${unbudgetedMsg}`,
1476
+ defaultActionLabel: unbudgetedTagsCount && unbudgetedTagsCount > 0 ? "Review Tags" : "View Budgets"
1477
+ };
1478
+ }
1479
+ if (budgetsUnder > 0 && budgetsOver === 0) {
1480
+ return {
1481
+ type: "success",
1482
+ title: "Budgets Under Control",
1483
+ message: `You're under budget by ${currencyFormatter(totalOverUnder)} total across ${budgetsUnder} budget${budgetsUnder > 1 ? "s" : ""}. Keep up the great work!`,
1484
+ defaultActionLabel: "View Details"
1485
+ };
1486
+ }
1487
+ return {
1488
+ type: "info",
1489
+ title: "Budgets Status",
1490
+ message: `${budgetsOnTrack} on track, ${budgetsUnder} under budget. You're managing your budgets well overall.`,
1491
+ defaultActionLabel: "View All Budgets"
1492
+ };
1493
+ }
1494
+ function BudgetOverviewMessage({
1495
+ data,
1496
+ onAction,
1497
+ actionLabel,
1498
+ currencyFormatter = defaultCurrencyFormatter4,
1499
+ hideAction = false
1500
+ }) {
1501
+ const overview = getOverviewMessage(data, currencyFormatter);
1502
+ const severityMap = {
1503
+ success: "success",
1504
+ warning: "warning",
1505
+ error: "error",
1506
+ info: "info"
1507
+ };
1508
+ return /* @__PURE__ */ jsxs(
1509
+ Alert,
1510
+ {
1511
+ "data-testid": "budget-overview-message",
1512
+ severity: severityMap[overview.type],
1513
+ sx: {
1514
+ "& .MuiAlert-message": {
1515
+ width: "100%"
1516
+ }
1517
+ },
1518
+ action: !hideAction && onAction ? /* @__PURE__ */ jsx(
1519
+ Button,
1520
+ {
1521
+ color: "inherit",
1522
+ size: "small",
1523
+ onClick: onAction,
1524
+ sx: { whiteSpace: "nowrap" },
1525
+ children: actionLabel || overview.defaultActionLabel
1526
+ }
1527
+ ) : void 0,
1528
+ children: [
1529
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", sx: { fontWeight: "bold", mb: 0.5 }, children: overview.title }),
1530
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", children: overview.message })
1531
+ ]
1532
+ }
1533
+ );
1534
+ }
1535
+ var steps = ["Basic Information", "Select Tags", "Review"];
1536
+ function validateBasicInfo(name, budgetAmount) {
1537
+ if (!name.trim()) {
1538
+ return "Budget name is required";
1539
+ }
1540
+ if (name.trim().length < 3) {
1541
+ return "Budget name must be at least 3 characters";
1542
+ }
1543
+ const amount = parseFloat(budgetAmount);
1544
+ if (isNaN(amount) || amount <= 0) {
1545
+ return "Budget amount must be a positive number";
1546
+ }
1547
+ if (amount > 1e6) {
1548
+ return "Budget amount cannot exceed $1,000,000";
1549
+ }
1550
+ return null;
1551
+ }
1552
+ function validateTags(tagNames) {
1553
+ if (tagNames.length === 0) {
1554
+ return "At least one tag is required";
1555
+ }
1556
+ if (tagNames.length > 10) {
1557
+ return "Maximum 10 tags allowed";
1558
+ }
1559
+ return null;
1560
+ }
1561
+ function BudgetStepper({
1562
+ initialData,
1563
+ availableTags,
1564
+ onSubmit,
1565
+ onCancel,
1566
+ editMode = false,
1567
+ submitting = false,
1568
+ error
1569
+ }) {
1570
+ const [activeStep, setActiveStep] = useState(0);
1571
+ const [name, setName] = useState(initialData?.name || "");
1572
+ const [budgetAmount, setBudgetAmount] = useState(
1573
+ initialData?.budgetAmount?.toString() || ""
1574
+ );
1575
+ const [tagNames, setTagNames] = useState(initialData?.tagNames || []);
1576
+ const [validationError, setValidationError] = useState(null);
1577
+ const handleNext = () => {
1578
+ setValidationError(null);
1579
+ if (activeStep === 0) {
1580
+ const error2 = validateBasicInfo(name, budgetAmount);
1581
+ if (error2) {
1582
+ setValidationError(error2);
1583
+ return;
1584
+ }
1585
+ } else if (activeStep === 1) {
1586
+ const error2 = validateTags(tagNames);
1587
+ if (error2) {
1588
+ setValidationError(error2);
1589
+ return;
1590
+ }
1591
+ }
1592
+ setActiveStep((prevStep) => prevStep + 1);
1593
+ };
1594
+ const handleBack = () => {
1595
+ setValidationError(null);
1596
+ setActiveStep((prevStep) => prevStep - 1);
1597
+ };
1598
+ const handleSubmit = async () => {
1599
+ const data = {
1600
+ name: name.trim(),
1601
+ budgetAmount: parseFloat(budgetAmount),
1602
+ tagNames,
1603
+ showOnDashboard: true
1604
+ };
1605
+ await onSubmit(data);
1606
+ };
1607
+ const renderStepContent = (step) => {
1608
+ switch (step) {
1609
+ case 0:
1610
+ return /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 3 }, children: [
1611
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: "Enter basic budget information" }),
1612
+ /* @__PURE__ */ jsx(
1613
+ TextField,
1614
+ {
1615
+ label: "Budget Name",
1616
+ value: name,
1617
+ onChange: (e) => {
1618
+ setName(e.target.value);
1619
+ setValidationError(null);
1620
+ },
1621
+ required: true,
1622
+ fullWidth: true,
1623
+ disabled: submitting,
1624
+ error: !!validationError && !name.trim(),
1625
+ helperText: "e.g., Monthly Groceries, Car Maintenance",
1626
+ inputProps: {
1627
+ maxLength: 255
1628
+ }
1629
+ }
1630
+ ),
1631
+ /* @__PURE__ */ jsx(
1632
+ TextField,
1633
+ {
1634
+ label: "Budget Amount",
1635
+ type: "number",
1636
+ value: budgetAmount,
1637
+ onChange: (e) => {
1638
+ setBudgetAmount(e.target.value);
1639
+ setValidationError(null);
1640
+ },
1641
+ required: true,
1642
+ fullWidth: true,
1643
+ disabled: submitting,
1644
+ error: !!validationError && !budgetAmount,
1645
+ helperText: "Maximum spending limit for this budget",
1646
+ InputProps: {
1647
+ startAdornment: /* @__PURE__ */ jsx(Typography, { sx: { mr: 0.5 }, children: "$" })
1648
+ },
1649
+ inputProps: {
1650
+ min: 0,
1651
+ step: 1
1652
+ }
1653
+ }
1654
+ )
1655
+ ] });
1656
+ case 1:
1657
+ return /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 3 }, children: [
1658
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: "Select tags to track spending against this budget" }),
1659
+ /* @__PURE__ */ jsx(
1660
+ Autocomplete,
1661
+ {
1662
+ multiple: true,
1663
+ options: availableTags,
1664
+ value: tagNames,
1665
+ onChange: (_, newValue) => {
1666
+ setTagNames(newValue);
1667
+ setValidationError(null);
1668
+ },
1669
+ disabled: submitting,
1670
+ renderInput: (params) => /* @__PURE__ */ jsx(
1671
+ TextField,
1672
+ {
1673
+ ...params,
1674
+ label: "Tags",
1675
+ placeholder: "Select tags...",
1676
+ required: true,
1677
+ error: !!validationError && tagNames.length === 0,
1678
+ helperText: "Choose tags related to this budget (e.g., Groceries, Dining Out)"
1679
+ }
1680
+ ),
1681
+ renderTags: (value, getTagProps) => value.map((option, index) => /* @__PURE__ */ createElement(
1682
+ Chip,
1683
+ {
1684
+ label: option,
1685
+ ...getTagProps({ index }),
1686
+ key: option
1687
+ }
1688
+ ))
1689
+ }
1690
+ ),
1691
+ tagNames.length > 0 && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "text.secondary", children: [
1692
+ tagNames.length,
1693
+ " tag",
1694
+ tagNames.length > 1 ? "s" : "",
1695
+ " selected"
1696
+ ] }) })
1697
+ ] });
1698
+ case 2:
1699
+ return /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 3 }, children: [
1700
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", color: "text.secondary", children: [
1701
+ "Review your budget before ",
1702
+ editMode ? "saving changes" : "creating"
1703
+ ] }),
1704
+ /* @__PURE__ */ jsxs(Paper, { sx: { p: 2, bgcolor: "background.default" }, children: [
1705
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", gutterBottom: true, children: "Budget Name" }),
1706
+ /* @__PURE__ */ jsx(Typography, { variant: "body1", sx: { mb: 2, fontWeight: "medium" }, children: name }),
1707
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", gutterBottom: true, children: "Budget Amount" }),
1708
+ /* @__PURE__ */ jsxs(Typography, { variant: "h6", color: "primary", sx: { mb: 2 }, children: [
1709
+ "$",
1710
+ parseFloat(budgetAmount).toLocaleString("en-US", {
1711
+ minimumFractionDigits: 2,
1712
+ maximumFractionDigits: 2
1713
+ })
1714
+ ] }),
1715
+ /* @__PURE__ */ jsxs(Typography, { variant: "subtitle2", gutterBottom: true, children: [
1716
+ "Tags (",
1717
+ tagNames.length,
1718
+ ")"
1719
+ ] }),
1720
+ /* @__PURE__ */ jsx(Box, { sx: { display: "flex", flexWrap: "wrap", gap: 1 }, children: tagNames.map((tag) => /* @__PURE__ */ jsx(Chip, { label: tag, size: "small" }, tag)) })
1721
+ ] })
1722
+ ] });
1723
+ default:
1724
+ return null;
1725
+ }
1726
+ };
1727
+ return /* @__PURE__ */ jsxs(Paper, { "data-testid": "budget-stepper", sx: { p: 3 }, children: [
1728
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", gutterBottom: true, children: editMode ? "Edit Budget" : "Create Budget" }),
1729
+ /* @__PURE__ */ jsx(Stepper, { activeStep, sx: { my: 3 }, children: steps.map((label) => /* @__PURE__ */ jsx(Step, { children: /* @__PURE__ */ jsx(StepLabel, { children: label }) }, label)) }),
1730
+ /* @__PURE__ */ jsxs(Box, { sx: { mt: 3, mb: 2 }, children: [
1731
+ (validationError || error) && /* @__PURE__ */ jsx(Alert, { severity: "error", sx: { mb: 2 }, children: validationError || error }),
1732
+ renderStepContent(activeStep)
1733
+ ] }),
1734
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", justifyContent: "space-between", mt: 3 }, children: [
1735
+ /* @__PURE__ */ jsx(Box, { children: onCancel && /* @__PURE__ */ jsx(Button, { onClick: onCancel, disabled: submitting, children: "Cancel" }) }),
1736
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", gap: 1 }, children: [
1737
+ /* @__PURE__ */ jsx(Button, { onClick: handleBack, disabled: activeStep === 0 || submitting, children: "Back" }),
1738
+ activeStep < steps.length - 1 ? /* @__PURE__ */ jsx(Button, { variant: "contained", onClick: handleNext, disabled: submitting, children: "Next" }) : /* @__PURE__ */ jsx(
1739
+ Button,
1740
+ {
1741
+ variant: "contained",
1742
+ onClick: handleSubmit,
1743
+ disabled: submitting,
1744
+ children: submitting ? editMode ? "Saving..." : "Creating..." : editMode ? "Save Changes" : "Create Budget"
1745
+ }
1746
+ )
1747
+ ] })
1748
+ ] })
1749
+ ] });
1750
+ }
1087
1751
 
1088
- export { BudgetCreateForm, BudgetDeleteButton, BudgetEditForm, BudgetInsightDialog, BudgetInsightsCard, BudgetList, BudgetProgressCard, BudgetProgressList, BudgetSummary };
1752
+ export { BudgetCreateForm, BudgetDeleteButton, BudgetEditForm, BudgetInsightDialog, BudgetInsightsCard, BudgetList, BudgetOverviewMessage, BudgetProgressCard, BudgetProgressList, BudgetStateIndicator, BudgetStepper, BudgetSummary, BudgetTagInsight };
1089
1753
  //# sourceMappingURL=index.js.map
1090
1754
  //# sourceMappingURL=index.js.map