Skip to content

Commit c41617c

Browse files
committed
feat(*.cs & *.xaml): Add debt management feature with store, viewmodel, and UI
Introduces a new debt management system including DebtStore and IDebtStore for handling debts, offers, and related API calls. Updates DashboardViewModel and DebtViewModel to use the new store, adds a NullToVisibilityInverseConverter, and updates DashboardView and DebtView to display active debts and pending offers. Implements a debt carousel on the dashboard and refactors debt-related logic out of DebtViewModel into the store for better separation of concerns.
1 parent ba00214 commit c41617c

File tree

8 files changed

+352
-188
lines changed

8 files changed

+352
-188
lines changed

FinTrack/App.xaml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using FinTrackForWindows.Services.Api;
66
using FinTrackForWindows.Services.Budgets;
77
using FinTrackForWindows.Services.Currencies;
8+
using FinTrackForWindows.Services.Debts;
89
using FinTrackForWindows.Services.Memberships;
910
using FinTrackForWindows.Services.Transactions;
1011
using FinTrackForWindows.ViewModels;
@@ -85,6 +86,7 @@ private void ConfigureServices(IServiceCollection services)
8586
services.AddSingleton<ITransactionStore, TransactionStore>();
8687
services.AddSingleton<ICurrenciesStore, CurrenciesStore>();
8788
services.AddSingleton<IMembershipStore, MembershipStore>();
89+
services.AddSingleton<IDebtStore, DebtStore>();
8890
}
8991

9092
protected override async void OnStartup(StartupEventArgs e)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Globalization;
2+
using System.Windows;
3+
using System.Windows.Data;
4+
5+
namespace FinTrackForWindows.Helpers
6+
{
7+
public class NullToVisibilityInverseConverter : IValueConverter
8+
{
9+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
10+
{
11+
return value == null ? Visibility.Visible : Visibility.Collapsed;
12+
}
13+
14+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
15+
{
16+
throw new NotImplementedException();
17+
}
18+
}
19+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
using CommunityToolkit.Mvvm.ComponentModel;
2+
using FinTrackForWindows.Core;
3+
using FinTrackForWindows.Dtos.DebtDtos;
4+
using FinTrackForWindows.Enums;
5+
using FinTrackForWindows.Models.Debt;
6+
using FinTrackForWindows.Services.Api;
7+
using Microsoft.Extensions.Logging;
8+
using System.Collections.ObjectModel;
9+
using System.IdentityModel.Tokens.Jwt;
10+
using System.Security.Claims;
11+
12+
namespace FinTrackForWindows.Services.Debts
13+
{
14+
public partial class DebtStore : ObservableObject, IDebtStore
15+
{
16+
private readonly IApiService _apiService;
17+
private readonly ILogger<DebtStore> _logger;
18+
private readonly int _currentUserId;
19+
20+
[ObservableProperty]
21+
private bool _isLoading;
22+
23+
private readonly ObservableCollection<DebtModel> _pendingOffers;
24+
public ReadOnlyObservableCollection<DebtModel> PendingOffers { get; }
25+
26+
private readonly ObservableCollection<DebtModel> _myDebtsList;
27+
public ReadOnlyObservableCollection<DebtModel> MyDebtsList { get; }
28+
29+
private readonly ObservableCollection<DebtModel> _activeDebts;
30+
public ReadOnlyObservableCollection<DebtModel> ActiveDebts { get; }
31+
32+
public event Action? DebtsChanged;
33+
34+
public DebtStore(IApiService apiService, ILogger<DebtStore> logger)
35+
{
36+
_apiService = apiService;
37+
_logger = logger;
38+
39+
_activeDebts = new ObservableCollection<DebtModel>();
40+
ActiveDebts = new ReadOnlyObservableCollection<DebtModel>(_activeDebts);
41+
42+
var handler = new JwtSecurityTokenHandler();
43+
var jsonToken = handler.ReadJwtToken(SessionManager.CurrentToken);
44+
_currentUserId = Convert.ToInt32(jsonToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value);
45+
46+
_pendingOffers = new ObservableCollection<DebtModel>();
47+
PendingOffers = new ReadOnlyObservableCollection<DebtModel>(_pendingOffers);
48+
49+
_myDebtsList = new ObservableCollection<DebtModel>();
50+
MyDebtsList = new ReadOnlyObservableCollection<DebtModel>(_myDebtsList);
51+
}
52+
53+
public async Task LoadDebtsAsync()
54+
{
55+
IsLoading = true;
56+
_logger.LogInformation("Loading debt data from API...");
57+
try
58+
{
59+
var debtDtos = await _apiService.GetAsync<List<DebtDto>>("Debt");
60+
if (debtDtos == null) return;
61+
62+
_pendingOffers.Clear();
63+
_myDebtsList.Clear();
64+
_activeDebts.Clear();
65+
66+
foreach (var dto in debtDtos)
67+
{
68+
var debtModel = new DebtModel
69+
{
70+
Id = dto.Id,
71+
LenderId = dto.LenderId,
72+
BorrowerId = dto.BorrowerId,
73+
CurrentUserId = _currentUserId,
74+
LenderName = dto.LenderName,
75+
BorrowerName = dto.BorrowerName,
76+
borrowerImageUrl = dto.BorrowerProfilePicture ?? "/Assets/Images/Icons/user-red.png",
77+
lenderImageUrl = dto.LenderProfilePicture ?? "/Assets/Images/Icons/user-green.png",
78+
Amount = dto.Amount,
79+
DueDate = dto.DueDateUtc.ToLocalTime(),
80+
Status = dto.Status
81+
};
82+
83+
if (dto.Status == DebtStatusType.PendingBorrowerAcceptance && dto.BorrowerId == _currentUserId)
84+
{
85+
_pendingOffers.Add(debtModel);
86+
}
87+
else
88+
{
89+
_myDebtsList.Add(debtModel);
90+
}
91+
92+
if (dto.Status == DebtStatusType.Active)
93+
{
94+
_activeDebts.Add(debtModel);
95+
}
96+
}
97+
DebtsChanged?.Invoke();
98+
_logger.LogInformation("Successfully loaded and processed {Count} debts.", debtDtos.Count);
99+
}
100+
catch (Exception ex)
101+
{
102+
_logger.LogError(ex, "Failed to load debt data.");
103+
}
104+
finally
105+
{
106+
IsLoading = false;
107+
}
108+
}
109+
110+
public async Task SendOfferAsync(string borrowerEmail, decimal amount, string currency, DateTime dueDate, string description)
111+
{
112+
IsLoading = true;
113+
try
114+
{
115+
var createDto = new CreateDebtOfferRequestDto
116+
{
117+
BorrowerEmail = borrowerEmail,
118+
Amount = amount,
119+
CurrencyCode = BaseCurrencyType.TRY,
120+
DueDateUtc = dueDate.ToUniversalTime(),
121+
Description = description
122+
};
123+
124+
_logger.LogInformation("Sending new debt offer to API...");
125+
await _apiService.PostAsync<object>("Debt/create-debt-offer", createDto);
126+
await LoadDebtsAsync();
127+
_logger.LogInformation("Debt offer sent successfully.");
128+
}
129+
catch (Exception ex)
130+
{
131+
_logger.LogError(ex, "Failed to send debt offer.");
132+
throw;
133+
}
134+
finally
135+
{
136+
IsLoading = false;
137+
}
138+
}
139+
140+
public async Task RespondToOfferAsync(DebtModel debt, bool accepted)
141+
{
142+
IsLoading = true;
143+
try
144+
{
145+
_logger.LogInformation("Responding to offer for DebtId: {DebtId} with decision: {Decision}", debt.Id, accepted);
146+
var requestBody = new { Accepted = accepted };
147+
await _apiService.PostAsync<bool>($"Debt/respond-to-offer/{debt.Id}", requestBody);
148+
await LoadDebtsAsync();
149+
_logger.LogInformation("Successfully responded to offer for DebtId: {DebtId}", debt.Id);
150+
}
151+
catch (Exception ex)
152+
{
153+
_logger.LogError(ex, "Failed to respond to debt offer for DebtId: {DebtId}", debt.Id);
154+
throw;
155+
}
156+
finally
157+
{
158+
IsLoading = false;
159+
}
160+
}
161+
162+
public async Task UploadVideoAsync(DebtModel debt, string filePath)
163+
{
164+
IsLoading = true;
165+
try
166+
{
167+
_logger.LogInformation("Uploading video for DebtId: {DebtId}", debt.Id);
168+
await _apiService.UploadFileAsync($"Videos/user-upload-video?debtId={debt.Id}", filePath);
169+
await LoadDebtsAsync();
170+
_logger.LogInformation("Video uploaded successfully for DebtId: {DebtId}", debt.Id);
171+
}
172+
catch (Exception ex)
173+
{
174+
_logger.LogError(ex, "Failed to upload video for DebtId: {DebtId}", debt.Id);
175+
throw;
176+
}
177+
finally
178+
{
179+
IsLoading = false;
180+
}
181+
}
182+
}
183+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using FinTrackForWindows.Models.Debt;
2+
using System.Collections.ObjectModel;
3+
4+
namespace FinTrackForWindows.Services.Debts
5+
{
6+
public interface IDebtStore
7+
{
8+
ReadOnlyObservableCollection<DebtModel> PendingOffers { get; }
9+
ReadOnlyObservableCollection<DebtModel> MyDebtsList { get; }
10+
ReadOnlyObservableCollection<DebtModel> ActiveDebts { get; }
11+
12+
bool IsLoading { get; }
13+
14+
Task LoadDebtsAsync();
15+
Task SendOfferAsync(string borrowerEmail, decimal amount, string currency, DateTime dueDate, string description);
16+
Task RespondToOfferAsync(DebtModel debt, bool accepted);
17+
Task UploadVideoAsync(DebtModel debt, string filePath);
18+
19+
event Action DebtsChanged;
20+
}
21+
}

0 commit comments

Comments
 (0)