@pfm-platform/budgets-ui-mui 0.1.1 → 0.2.0

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