diff --git a/MoneyMap/Pages/CategoryMappings.cshtml b/MoneyMap/Pages/CategoryMappings.cshtml
index 7df633c..e91eed0 100644
--- a/MoneyMap/Pages/CategoryMappings.cshtml
+++ b/MoneyMap/Pages/CategoryMappings.cshtml
@@ -146,9 +146,9 @@ else
-
-
-
+
+
-
-
+
+
Higher priority = checked first (0 = normal, 100 = high, 200 = critical)
-
@@ -195,12 +192,12 @@ else
@@ -360,4 +356,4 @@ else
}
});
-}
\ No newline at end of file
+}
diff --git a/MoneyMap/Pages/CategoryMappings.cshtml.cs b/MoneyMap/Pages/CategoryMappings.cshtml.cs
index 47747b4..c923925 100644
--- a/MoneyMap/Pages/CategoryMappings.cshtml.cs
+++ b/MoneyMap/Pages/CategoryMappings.cshtml.cs
@@ -25,12 +25,6 @@ namespace MoneyMap.Pages
public int TotalMappings { get; set; }
public int TotalCategories { get; set; }
- [BindProperty]
- public MappingEditModel NewMapping { get; set; } = new();
-
- [BindProperty]
- public MappingEditModel EditMapping { get; set; } = new();
-
[TempData]
public string? SuccessMessage { get; set; }
@@ -49,13 +43,8 @@ namespace MoneyMap.Pages
return RedirectToPage();
}
- public async Task
OnPostAddMappingAsync()
+ public async Task OnPostAddMappingAsync(AddMappingModel model)
{
- // Remove validation errors for EditMapping since we're not using it in this handler
- ModelState.Remove("EditMapping.Category");
- ModelState.Remove("EditMapping.Pattern");
- ModelState.Remove("EditMapping.Priority");
-
if (!ModelState.IsValid)
{
ErrorMessage = "Please fill in all required fields.";
@@ -65,9 +54,9 @@ namespace MoneyMap.Pages
var mapping = new CategoryMapping
{
- Category = NewMapping.Category.Trim(),
- Pattern = NewMapping.Pattern.Trim(),
- Priority = NewMapping.Priority
+ Category = model.Category.Trim(),
+ Pattern = model.Pattern.Trim(),
+ Priority = model.Priority
};
_db.CategoryMappings.Add(mapping);
@@ -77,13 +66,8 @@ namespace MoneyMap.Pages
return RedirectToPage();
}
- public async Task OnPostUpdateMappingAsync()
+ public async Task OnPostUpdateMappingAsync(UpdateMappingModel model)
{
- // Remove validation errors for NewMapping since we're not using it in this handler
- ModelState.Remove("NewMapping.Category");
- ModelState.Remove("NewMapping.Pattern");
- ModelState.Remove("NewMapping.Priority");
-
if (!ModelState.IsValid)
{
ErrorMessage = "Please fill in all required fields.";
@@ -91,16 +75,16 @@ namespace MoneyMap.Pages
return Page();
}
- var mapping = await _db.CategoryMappings.FindAsync(EditMapping.Id);
+ var mapping = await _db.CategoryMappings.FindAsync(model.Id);
if (mapping == null)
{
ErrorMessage = "Mapping not found.";
return RedirectToPage();
}
- mapping.Category = EditMapping.Category.Trim();
- mapping.Pattern = EditMapping.Pattern.Trim();
- mapping.Priority = EditMapping.Priority;
+ mapping.Category = model.Category.Trim();
+ mapping.Pattern = model.Pattern.Trim();
+ mapping.Priority = model.Priority;
await _db.SaveChangesAsync();
@@ -150,15 +134,29 @@ namespace MoneyMap.Pages
public int Count { get; set; }
}
- public class MappingEditModel
+ public class AddMappingModel
{
- public int Id { get; set; }
-
- [Required]
+ [Required(ErrorMessage = "Category is required")]
[StringLength(100)]
public string Category { get; set; } = "";
+ [Required(ErrorMessage = "Pattern is required")]
+ [StringLength(200)]
+ public string Pattern { get; set; } = "";
+
+ public int Priority { get; set; } = 0;
+ }
+
+ public class UpdateMappingModel
+ {
[Required]
+ public int Id { get; set; }
+
+ [Required(ErrorMessage = "Category is required")]
+ [StringLength(100)]
+ public string Category { get; set; } = "";
+
+ [Required(ErrorMessage = "Pattern is required")]
[StringLength(200)]
public string Pattern { get; set; } = "";
diff --git a/MoneyMap/Pages/Index.cshtml b/MoneyMap/Pages/Index.cshtml
index 4ed5abd..f25cac3 100644
--- a/MoneyMap/Pages/Index.cshtml
+++ b/MoneyMap/Pages/Index.cshtml
@@ -61,7 +61,10 @@
@if (Model.TopCategories.Any())
{
-
+
diff --git a/MoneyMap/Pages/Index.cshtml.cs b/MoneyMap/Pages/Index.cshtml.cs
index ab5fcf7..b55e0bd 100644
--- a/MoneyMap/Pages/Index.cshtml.cs
+++ b/MoneyMap/Pages/Index.cshtml.cs
@@ -178,6 +178,7 @@ namespace MoneyMap.Pages
return await _db.Transactions
.Where(t => t.Date >= since && t.Amount < 0)
+ .ExcludeTransfers() // Exclude credit card payments and transfers
.GroupBy(t => t.Category ?? "")
.Select(g => new IndexModel.TopCategoryRow
{
@@ -236,7 +237,7 @@ namespace MoneyMap.Pages
if (string.IsNullOrEmpty(cardLast4))
return "";
- return $"•••• {cardLast4}";
+ return $"���� {cardLast4}";
}
}
diff --git a/MoneyMap/Services/TransactionFilters.cs b/MoneyMap/Services/TransactionFilters.cs
new file mode 100644
index 0000000..5075121
--- /dev/null
+++ b/MoneyMap/Services/TransactionFilters.cs
@@ -0,0 +1,37 @@
+using System.Linq;
+using MoneyMap.Models;
+
+namespace MoneyMap.Services
+{
+ ///
+ /// Helper class for filtering transactions in queries
+ ///
+ public static class TransactionFilters
+ {
+ ///
+ /// Categories that represent transfers between accounts, not actual spending.
+ /// These should be excluded from spending reports and analytics.
+ ///
+ public static readonly string[] TransferCategories = new[]
+ {
+ "Credit Card Payment",
+ "Banking" // Includes ATM withdrawals, transfers, fees that offset elsewhere
+ };
+
+ ///
+ /// Filter to exclude transfer transactions from spending queries
+ ///
+ public static IQueryable ExcludeTransfers(this IQueryable query)
+ {
+ return query.Where(t => !TransferCategories.Contains(t.Category ?? ""));
+ }
+
+ ///
+ /// Check if a category represents a transfer (not actual spending)
+ ///
+ public static bool IsTransferCategory(string? category)
+ {
+ return TransferCategories.Contains(category ?? "");
+ }
+ }
+}