Files
Clario/Clario/ViewModels/TransactionsViewModel.cs

366 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Clario.Data;
using Clario.Messages;
using Clario.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
namespace Clario.ViewModels;
public partial class TransactionsViewModel : ViewModelBase
{
public required ViewModelBase parentViewModel;
public List<Transaction> AllTransactions = new();
[ObservableProperty] private ObservableCollection<Category> _categories = new();
[ObservableProperty] private ObservableCollection<Account> _accounts = new();
[ObservableProperty] private List<Transaction> _filteredTransactions = new();
private int _pageSize = 25;
[ObservableProperty] private int _pageSizeIndex = 0;
[ObservableProperty] [NotifyPropertyChangedFor(nameof(TotalPages))] [NotifyCanExecuteChangedFor(nameof(NextPageCommand), nameof(PreviousPageCommand))]
private int _currentPage = 1;
[ObservableProperty] private string _paginationSummaryText;
[ObservableProperty] private ObservableCollection<Transaction> _pagedTransactions = new();
[ObservableProperty] private ObservableCollection<string> _sortOptions = new()
{
"Date — Newest first",
"Date — Oldest first",
"Amount — High to low",
"Amount — Low to high",
"Category A → Z"
};
[ObservableProperty] private ObservableCollection<string> _DateRangeOptions = new()
{
"All Time",
"Today",
"This Week",
"This Month",
"Last Month",
"This Quarter",
"This Year",
"Custom Range"
};
public List<int> PageNumbers { get; set; }
[ObservableProperty] private ObservableCollection<int> _visiblePageNumbers = new();
public int TotalPages => (int)Math.Ceiling(_filteredTransactions.Count / (double)_pageSize);
public bool HasNoTransactions => _filteredTransactions.Count == 0;
public bool HasNextPage => CurrentPage < TotalPages;
public bool HasPreviousPage => CurrentPage > 1;
[ObservableProperty] private double _totalExpenses;
[ObservableProperty] private double _totalIncome;
[ObservableProperty] private int _expensesCount;
[ObservableProperty] private int _incomeCount;
[ObservableProperty] private string _searchText = "";
[ObservableProperty] private Category _selectedCategory;
[ObservableProperty] private Account _selectedAccount;
[ObservableProperty] private string _selectedSortOption;
[ObservableProperty] private string _selectedDateRangeOption;
[ObservableProperty] private List<DateTime>? _selectedDates = new()
{
new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1),
new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month))
};
[ObservableProperty] [NotifyPropertyChangedFor(nameof(FilterTypeAll), nameof(FilterTypeIncome), nameof(FilterTypeExpense))]
private string _transactionType = "all";
public TransactionsViewModel()
{
}
partial void OnPageSizeIndexChanged(int value)
{
_pageSize = value switch
{
0 => 25,
1 => 50,
2 => 100,
_ => 25
};
LoadPage(1);
OnPropertyChanged(nameof(HasNextPage));
OnPropertyChanged(nameof(HasPreviousPage));
}
partial void OnCurrentPageChanged(int value)
{
LoadPage(value);
OnPropertyChanged(nameof(HasNextPage));
OnPropertyChanged(nameof(HasPreviousPage));
}
[RelayCommand]
private void LoadPageStr(string page)
{
LoadPage(int.Parse(page));
}
[RelayCommand]
private void LoadPage(int page)
{
ApplyFilters();
if (CurrentPage != page) CurrentPage = page;
var items = _filteredTransactions
.Skip((page - 1) * _pageSize)
.Take(_pageSize);
OnPropertyChanged(nameof(HasNoTransactions));
PagedTransactions.Clear();
foreach (var item in items)
PagedTransactions.Add(item);
PaginationSummaryText =
$"Showing {((page - 1) * _pageSize) + 1}-{(Math.Min(page * _pageSize, _filteredTransactions.Count))} of {_filteredTransactions.Count} transactions";
PageNumbers = Enumerable.Range(1, Math.Min(TotalPages, 5)).ToList();
var numbers = GetSurrounding(PageNumbers, page, 5);
VisiblePageNumbers.Clear();
foreach (var number in numbers)
VisiblePageNumbers.Add(number);
WeakReferenceMessenger.Default.Send(new TransactionsScrollToTop());
GroupTransactions();
}
[RelayCommand]
private void ApplyFilters()
{
// Console.WriteLine($"Search Text: {_searchText}");
// Console.WriteLine($"Category: {_selectedCategory.Name}");
// Console.WriteLine($"Account: {_selectedAccount.Name}");
// Console.WriteLine($"Transaction Type: {_transactionType}");
var filtered = AllTransactions.Where(x =>
x.Description.Contains(_searchText, StringComparison.OrdinalIgnoreCase)
|| x.Note.Contains(_searchText, StringComparison.OrdinalIgnoreCase));
switch (SelectedDateRangeOption)
{
case "All Time":
// do nothing
break;
case "Today":
filtered = filtered.Where(x => x.Date == DateTime.Now.Date);
break;
case "This Week":
var startOfWeek = DateTime.Now.Date.AddDays(-(int)DateTime.Now.DayOfWeek);
var endOfWeek = startOfWeek.AddDays(6);
filtered = filtered.Where(x => x.Date.Date >= startOfWeek && x.Date.Date <= endOfWeek);
break;
case "This Month":
filtered = filtered.Where(x => x.Date.Month == DateTime.Now.Month);
break;
case "Last Month":
var lastMonth = DateTime.Now.AddMonths(-1);
filtered = filtered.Where(x => x.Date.Month == lastMonth.Month && x.Date.Year == lastMonth.Year);
break;
case "This Quarter":
var startOfQuarter = DateTime.Now.AddMonths(-(DateTime.Now.Month - 1) % 3);
var endOfQuarter = startOfQuarter.AddMonths(3);
filtered = filtered.Where(x => x.Date >= startOfQuarter && x.Date <= endOfQuarter);
break;
case "This Year":
filtered = filtered.Where(x => x.Date.Year == DateTime.Now.Year);
break;
case "Custom Range":
if (SelectedDates is not null && SelectedDates.Count > 0)
{
var ordered = SelectedDates
.Select(d => d.Date)
.Distinct()
.OrderBy(d => d)
.ToList();
var start = ordered.First();
var end = ordered.Last();
Console.WriteLine($"first {SelectedDates.First():d} / last {SelectedDates.Last():d}");
if (SelectedDates.Count == 1)
filtered = filtered.Where(x => x.Date.Date == start);
else
filtered = filtered.Where(x => x.Date.Date >= start && x.Date.Date <= end);
}
break;
}
if (_selectedCategory.Name != "All Categories")
filtered = filtered.Where(x => x.CategoryId == _selectedCategory.Id);
if (_selectedAccount.Name != "All Accounts")
filtered = filtered.Where(x => x.AccountId == _selectedAccount.Id);
if (_transactionType != "all")
filtered = filtered.Where(x => x.Type == _transactionType);
switch (SelectedSortOption)
{
case "Date — Newest first":
filtered = filtered.OrderByDescending(x => x.Date);
break;
case "Date — Oldest first":
filtered = filtered.OrderBy(x => x.Date);
break;
case "Amount — High to low":
filtered = filtered.OrderByDescending(x => x.Amount);
break;
case "Amount — Low to high":
filtered = filtered.OrderBy(x => x.Amount);
break;
case "Category A → Z":
filtered = filtered.OrderBy(x => x.Category?.Name);
break;
}
FilteredTransactions = filtered.ToList();
}
[RelayCommand]
private void ResetFilters()
{
SearchText = "";
SelectedCategory = Categories.First();
SelectedAccount = Accounts.First();
TransactionType = "all";
SelectedSortOption = SortOptions.First();
SelectedDateRangeOption = DateRangeOptions.First();
LoadPage(1);
}
[RelayCommand]
private void SetTransactionType(string type)
{
TransactionType = type;
}
public bool FilterTypeAll => TransactionType == "all";
public bool FilterTypeIncome => TransactionType == "income";
public bool FilterTypeExpense => TransactionType == "expense";
[RelayCommand(CanExecute = nameof(HasNextPage))]
private void NextPage()
{
if (CurrentPage < TotalPages) CurrentPage++;
}
[RelayCommand(CanExecute = nameof(HasPreviousPage))]
private void PreviousPage()
{
if (CurrentPage > 1) CurrentPage--;
}
private void GroupTransactions()
{
var ToRemove = PagedTransactions.Where(x => x.GroupHeader).ToList();
foreach (var item in ToRemove)
{
PagedTransactions.Remove(item);
}
var dates = PagedTransactions
.Where(x => !x.GroupHeader)
.Select(x => x.Date.Date) // strip time
.Distinct()
.ToList();
foreach (var date in dates)
{
var index = PagedTransactions.IndexOf(PagedTransactions.First(x => x.Date.Date == date && !x.GroupHeader));
string label;
var culture = new CultureInfo("en-US");
if (date.Date == DateTime.Now.Date) label = "Today - " + date.ToString("MMM dd", culture);
else if (date.Date == DateTime.Now.AddDays(-1).Date) label = "Yesterday - " + date.ToString("MMM dd", culture);
else label = date.ToString("MMM dd, yyyy", culture);
var header = new Transaction { Description = label, Date = date, GroupHeader = true };
PagedTransactions.Insert(index, header);
}
}
public async Task Initialize()
{
try
{
InitializeCategories();
InitializeAccounts();
CalculateMonthlyFinancials();
CurrentPage = 1;
OnPropertyChanged(nameof(TotalPages));
ResetFilters();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
private void InitializeCategories()
{
Categories.Insert(0, new Category() { Name = "All Categories" });
SelectedCategory = Categories.First();
}
private void InitializeAccounts()
{
Accounts.Insert(0, new Account() { Name = "All Accounts" });
SelectedAccount = Accounts.First();
}
private void CalculateMonthlyFinancials()
{
TotalExpenses = AllTransactions.Where(x => x.Type == "expense" && x.Date.Month == DateTime.Now.Month).Sum(x => Convert.ToDouble(x.Amount));
TotalIncome = AllTransactions.Where(x => x.Type == "income" && x.Date.Month == DateTime.Now.Month).Sum(x => Convert.ToDouble(x.Amount));
ExpensesCount = AllTransactions.Count(x => x.Type == "expense" && x.Date.Month == DateTime.Now.Month);
IncomeCount = AllTransactions.Count(x => x.Type == "income" && x.Date.Month == DateTime.Now.Month);
}
public static List<T> GetSurrounding<T>(List<T> list, T item, int count = 5)
{
var index = list.IndexOf(item);
if (index == -1) return new List<T>();
var half = count / 2;
var start = Math.Max(0, index - half);
var end = Math.Min(list.Count, start + count);
// shift start back if end hit the boundary
start = Math.Max(0, end - count);
return list.GetRange(start, end - start);
}
[RelayCommand]
private void CreateTransaction()
{
((MainViewModel)parentViewModel).OpenAddTransaction();
}
[RelayCommand]
private void EditTransaction(Transaction transaction)
{
((MainViewModel)parentViewModel).OpenEditTransaction(transaction);
}
}