Files
Clario/Clario/ViewModels/TransactionFormViewModel.cs

259 lines
8.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Clario.Data;
using Clario.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace Clario.ViewModels;
public partial class TransactionFormViewModel : ViewModelBase
{
public required ViewModelBase parentViewModel;
public GeneralDataRepo AppData => DataRepo.General;
// ── Mode ────────────────────────────────────────────────
[ObservableProperty] [NotifyPropertyChangedFor(nameof(FormTitle), nameof(FormSubtitle), nameof(SaveButtonLabel))]
private bool _isEditMode = false;
public string FormTitle => IsEditMode ? "Edit Transaction" : "New Transaction";
public string FormSubtitle => IsEditMode ? "Update the details below" : "Fill in the details below";
public string SaveButtonLabel => IsEditMode ? "Save Changes" : "Save Transaction";
// ── Fields ──────────────────────────────────────────────
[ObservableProperty] [NotifyPropertyChangedFor(nameof(IsExpense), nameof(IsIncome), nameof(IsValid))]
private string _type = "expense";
[ObservableProperty] [NotifyPropertyChangedFor(nameof(IsValid))]
private string _amount = "";
[ObservableProperty] [NotifyPropertyChangedFor(nameof(IsValid))]
private string _description = "";
[ObservableProperty] private string? _note;
[ObservableProperty] private List<DateTime> _dates = [DateTime.Now];
[ObservableProperty] private DateTime? _selectedDate;
[ObservableProperty] private string _currency = "USD";
[ObservableProperty] [NotifyPropertyChangedFor(nameof(IsValid))]
private Category? _selectedCategory;
[ObservableProperty] [NotifyPropertyChangedFor(nameof(IsValid))]
private Account? _selectedAccount;
[ObservableProperty] private ObservableCollection<Category> _categories = new();
[ObservableProperty] private ObservableCollection<Account> _accounts = new();
// ── Validation ──────────────────────────────────────────
[ObservableProperty] [NotifyPropertyChangedFor(nameof(HasError))]
private string? _errorMessage;
public bool HasError => !string.IsNullOrEmpty(ErrorMessage);
public bool IsExpense => Type == "expense";
public bool IsIncome => Type == "income";
public bool IsValid =>
decimal.TryParse(Amount, out var amt) && amt > 0 &&
!string.IsNullOrWhiteSpace(Description) &&
SelectedCategory is not null &&
SelectedAccount is not null &&
Dates is not null;
// ── Callbacks ───────────────────────────────────────────
public Action? OnSaved;
public Action? OnCancelled;
public Action? OnDeleted;
[ObservableProperty] private bool _showDeleteConfirm = false;
// ── Edit mode: original transaction ─────────────────────
private Transaction? _editingTransaction;
private Guid? _editingId;
// ── Result transaction ──────────────────────────────────
public Transaction? ResultTransaction { get; set; }
// ── Commands ────────────────────────────────────────────
partial void OnSelectedCategoryChanged(Category? value)
{
if (value.Type == Type) return;
Type = value.Type;
}
partial void OnTypeChanged(string value)
{
if (value == SelectedCategory?.Type) return;
SelectedCategory = _categories.FirstOrDefault(c => c.Type == value);
}
[RelayCommand]
private void SetType(string type)
{
Type = type;
}
[RelayCommand]
private void SetToday()
{
Dates = [DateTime.Now];
}
[RelayCommand]
private async Task Save()
{
ErrorMessage = null;
if (!decimal.TryParse(Amount, out var amt) || amt <= 0)
{
ErrorMessage = "Please enter a valid amount.";
return;
}
if (string.IsNullOrWhiteSpace(Description))
{
ErrorMessage = "Description is required.";
return;
}
if (SelectedCategory is null)
{
ErrorMessage = "Please select a category.";
return;
}
if (SelectedAccount is null)
{
ErrorMessage = "Please select an account.";
return;
}
try
{
if (IsEditMode && _editingId.HasValue)
{
var updated = new Transaction
{
Id = _editingId.Value,
UserId = Guid.Parse(Services.SupabaseService.Client.Auth.CurrentUser!.Id),
Type = Type,
Amount = amt,
Description = Description.Trim(),
Note = Note?.Trim(),
Date = Dates.FirstOrDefault(),
CategoryId = SelectedCategory.Id,
AccountId = SelectedAccount.Id,
Category = SelectedCategory,
};
await DataRepo.General.UpdateTransaction(updated);
ResultTransaction = updated;
}
else
{
var transaction = new Transaction
{
Id = Guid.NewGuid(),
UserId = Guid.Parse(Services.SupabaseService.Client.Auth.CurrentUser!.Id!),
Type = Type,
Amount = amt,
Description = Description.Trim(),
Note = Note?.Trim(),
Date = Dates.FirstOrDefault(),
CategoryId = SelectedCategory.Id,
AccountId = SelectedAccount.Id,
Category = SelectedCategory,
};
await DataRepo.General.InsertTransaction(transaction);
ResultTransaction = transaction;
}
OnSaved?.Invoke();
}
catch (Exception ex)
{
ErrorMessage = "Something went wrong. Please try again.";
Console.WriteLine(ex);
}
}
[RelayCommand]
private async Task ConfirmDelete()
{
if (!IsEditMode || !_editingId.HasValue) return;
try
{
await DataRepo.General.DeleteTransaction(_editingId.Value);
OnDeleted?.Invoke();
}
catch (Exception ex)
{
ErrorMessage = "Failed to delete transaction.";
Console.WriteLine(ex);
}
}
[RelayCommand]
private void RequestDelete()
{
ShowDeleteConfirm = true;
}
[RelayCommand]
private void CancelDelete()
{
ShowDeleteConfirm = false;
}
[RelayCommand]
private void Cancel()
{
OnCancelled?.Invoke();
}
// ── Public setup methods ─────────────────────────────────
/// <summary>Call this to open the form for adding a new transaction.</summary>
public void SetupForAdd()
{
ShowDeleteConfirm = false;
IsEditMode = false;
_editingId = null;
Categories = AppData.Categories;
Accounts = AppData.Accounts;
Type = "expense";
Amount = "";
Description = "";
Note = null;
Dates = [DateTime.Now];
ErrorMessage = null;
SelectedCategory = AppData.Categories.Count > 0 ? AppData.Categories[0] : null;
SelectedAccount = AppData.Accounts.Count > 0 ? AppData.Accounts[0] : null;
ResultTransaction = null;
}
/// <summary>Call this to open the form for editing an existing transaction.</summary>
public void SetupForEdit(
Transaction transaction)
{
ShowDeleteConfirm = false;
IsEditMode = true;
_editingId = transaction.Id;
Categories = AppData.Categories;
Accounts = AppData.Accounts;
Type = transaction.Type;
Amount = transaction.Amount.ToString("0.00");
Description = transaction.Description;
Note = transaction.Note;
Dates = [transaction.Date];
ErrorMessage = null;
SelectedCategory = AppData.Categories.FirstOrDefault(c => c.Id == transaction.CategoryId)
?? (AppData.Categories.Count > 0 ? AppData.Categories[0] : null);
SelectedAccount = AppData.Accounts.FirstOrDefault(a => a.Id == transaction.AccountId)
?? (AppData.Accounts.Count > 0 ? AppData.Accounts[0] : null);
ResultTransaction = transaction;
}
}