Skip to content

Commit 56f97eb

Browse files
committed
feat(*.xaml, *.cs): Integrate account statistics chart and refactor account model
Added LiveCharts-based statistics chart to AccountView, including a new BrushToLvcPaintConverter helper. Refactored AccountModel to use BaseCurrencyType and made balance nullable. Removed Balance from AccountCreateDto and AccountUpdateDto. Enhanced AccountViewModel to support chart data, currency/account type selection, and improved transaction history loading. Updated AccountType enum with descriptions and added ScottPlot.WPF package.
1 parent 443738e commit 56f97eb

File tree

9 files changed

+249
-40
lines changed

9 files changed

+249
-40
lines changed

FinTrack/App.xaml.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using FinTrackForWindows.Services.Api;
55
using FinTrackForWindows.ViewModels;
66
using FinTrackForWindows.Views;
7-
using Microsoft.Extensions.Configuration;
87
using Microsoft.Extensions.DependencyInjection;
98
using Microsoft.Extensions.Hosting;
109
using Microsoft.Extensions.Logging;

FinTrack/Dtos/AccountDtos/AccountCreateDto.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ public class AccountCreateDto
77
public string Name { get; set; } = null!;
88
public AccountType Type { get; set; }
99
public bool IsActive { get; set; }
10-
public decimal Balance { get; set; }
1110
public BaseCurrencyType Currency { get; set; }
1211
}
1312
}

FinTrack/Dtos/AccountDtos/AccountUpdateDto.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ public class AccountUpdateDto
77
public string Name { get; set; } = null!;
88
public AccountType Type { get; set; }
99
public bool IsActive { get; set; }
10-
public decimal Balance { get; set; }
1110
public BaseCurrencyType Currency { get; set; }
1211
}
1312
}

FinTrack/Enums/AccountType.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
1-
namespace FinTrackForWindows.Enums
1+
using System.ComponentModel;
2+
3+
namespace FinTrackForWindows.Enums
24
{
35
public enum AccountType
46
{
7+
[Description("Checking")]
58
Checking,
9+
10+
[Description("Savings")]
611
Savings,
12+
13+
[Description("Credit Card")]
714
CreditCard,
15+
16+
[Description("Cash")]
817
Cash,
18+
19+
[Description("Investment")]
920
Investment,
21+
22+
[Description("Loan")]
1023
Loan,
24+
25+
[Description("Other")]
1126
Other,
1227
} // Kontrol, Tasarruf, Kredi Kartı, Nakit, Yatırım, Kredi, Diğer
1328
}

FinTrack/FinTrackForWindows.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-preview.6.25358.103" />
5656
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
5757
<PackageReference Include="Npgsql" Version="9.0.3" />
58+
<PackageReference Include="ScottPlot.WPF" Version="5.0.55" />
5859
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.1-dev-02307" />
5960
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.1-dev-02317" />
6061
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using LiveChartsCore.SkiaSharpView.Painting;
2+
using SkiaSharp;
3+
using System.Globalization;
4+
using System.Windows.Data;
5+
using System.Windows.Media;
6+
7+
namespace FinTrackForWindows.Helpers
8+
{
9+
public class BrushToLvcPaintConverter : IValueConverter
10+
{
11+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
12+
{
13+
if (value is SolidColorBrush brush)
14+
{
15+
return new SolidColorPaint(new SKColor(brush.Color.R, brush.Color.G, brush.Color.B, brush.Color.A));
16+
}
17+
return new SolidColorPaint(SKColors.Black);
18+
}
19+
20+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
21+
{
22+
throw new NotImplementedException();
23+
}
24+
}
25+
}

FinTrack/Models/Account/AccountModel.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@ public partial class AccountModel : ObservableObject
1616
private AccountType type;
1717

1818
[ObservableProperty]
19-
private decimal balance;
20-
21-
[ObservableProperty]
22-
private string currency = "USD";
19+
private BaseCurrencyType currency;
2320

2421
[ObservableProperty]
2522
private List<AccountBalanceHistoryPoint> history = new();
2623

24+
public decimal? balance { get; set; }
25+
2726
public string IconPath => Type switch
2827
{
2928
AccountType.Checking => "/Assets/Images/Icons/bank.png",
@@ -44,7 +43,7 @@ public string BalanceText
4443
{
4544
get
4645
{
47-
var balance = Balance.ToString("C", System.Globalization.CultureInfo.CurrentCulture);
46+
var balanceString = balance.ToString();
4847
return Type switch
4948
{
5049
AccountType.Checking => $"Bakiye: {balance}",

FinTrack/ViewModels/AccountViewModel.cs

Lines changed: 169 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,37 @@
11
using CommunityToolkit.Mvvm.ComponentModel;
22
using CommunityToolkit.Mvvm.Input;
33
using FinTrackForWindows.Dtos.AccountDtos;
4+
using FinTrackForWindows.Dtos.TransactionDtos;
45
using FinTrackForWindows.Enums;
56
using FinTrackForWindows.Models.Account;
67
using FinTrackForWindows.Services.Api;
8+
using LiveChartsCore;
9+
using LiveChartsCore.Defaults;
10+
using LiveChartsCore.SkiaSharpView;
11+
using LiveChartsCore.SkiaSharpView.Painting;
12+
using LiveChartsCore.SkiaSharpView.VisualElements;
713
using Microsoft.Extensions.Logging;
14+
using SkiaSharp;
815
using System.Collections.ObjectModel;
16+
using System.Linq;
17+
using System.Threading.Tasks;
918

1019
namespace FinTrackForWindows.ViewModels
1120
{
1221
public partial class AccountViewModel : ObservableObject
1322
{
1423
[ObservableProperty]
15-
private ObservableCollection<AccountModel> accounts;
24+
private ObservableCollection<AccountModel> accounts = new();
1625

1726
[ObservableProperty]
1827
[NotifyPropertyChangedFor(nameof(FormTitle))]
1928
[NotifyPropertyChangedFor(nameof(SaveButtonText))]
2029
private AccountModel? selectedAccount;
2130

31+
public ObservableCollection<BaseCurrencyType> CurrencyTypes { get; }
32+
33+
public ObservableCollection<AccountType> AccountTypes { get; }
34+
2235
private readonly ILogger<AccountViewModel> _logger;
2336

2437
public string FormTitle => IsEditing ? "Hesabı Düzenle" : "Yeni Hesap Ekle";
@@ -28,22 +41,78 @@ public partial class AccountViewModel : ObservableObject
2841

2942
private readonly IApiService _apiService;
3043

44+
[ObservableProperty]
45+
private ISeries[] series = new ISeries[0];
46+
47+
[ObservableProperty]
48+
private Axis[] xAxes = new Axis[0];
49+
50+
[ObservableProperty]
51+
private Axis[] yAxes = new Axis[0];
52+
53+
[ObservableProperty]
54+
private LabelVisual title = new LabelVisual { /* Başlık için yer tutucu */ };
55+
3156
public AccountViewModel(ILogger<AccountViewModel> logger, IApiService apiService)
3257
{
3358
_logger = logger;
3459
_apiService = apiService;
60+
61+
InitializeEmptyChart();
3562
_ = LoadData();
63+
_ = InitializeViewModel();
3664
PrepareForNewAccount();
65+
66+
CurrencyTypes = new ObservableCollection<BaseCurrencyType>();
67+
AccountTypes = new ObservableCollection<AccountType>();
68+
69+
CurrencyTypes.Clear();
70+
foreach (BaseCurrencyType currencyType in Enum.GetValues(typeof(BaseCurrencyType)))
71+
{
72+
CurrencyTypes.Add(currencyType);
73+
}
74+
}
75+
76+
private void InitializeEmptyChart()
77+
{
78+
Series = new ISeries[0];
79+
Title = new LabelVisual
80+
{
81+
Text = "Verileri görmek için bir hesap seçin.",
82+
TextSize = 18,
83+
Paint = new SolidColorPaint(SKColors.Gray),
84+
Padding = new LiveChartsCore.Drawing.Padding(15)
85+
};
86+
}
87+
88+
private async Task InitializeViewModel()
89+
{
90+
await LoadData();
91+
if (Accounts.Any())
92+
{
93+
var firstAccount = Accounts.First();
94+
if (firstAccount.Id.HasValue)
95+
{
96+
SelectedAccount = firstAccount;
97+
}
98+
}
3799
}
38100

39101
partial void OnSelectedAccountChanged(AccountModel? value)
40102
{
41103
_logger.LogInformation("Seçilen hesap değişti: {AccountName}", value?.Name ?? "Hiçbiri");
42-
43104
IsEditing = value != null && value.Id != null;
44-
45105
OnPropertyChanged(nameof(FormTitle));
46106
OnPropertyChanged(nameof(SaveButtonText));
107+
108+
if (value != null && value.Id.HasValue)
109+
{
110+
_ = LoadTransactionHistory(value.Id.Value, value.Name);
111+
}
112+
else
113+
{
114+
InitializeEmptyChart();
115+
}
47116
}
48117

49118
private async Task LoadData()
@@ -59,44 +128,126 @@ private async Task LoadData()
59128
Id = item.Id,
60129
Name = item.Name,
61130
Type = item.Type,
62-
Balance = item.Balance,
63-
Currency = item.Currency.ToString(),
131+
balance = item.Balance,
132+
Currency = item.Currency,
64133
});
65134
}
66135
}
67136
}
68137

69-
[RelayCommand]
70-
private async Task SaveAccount()
138+
private async Task LoadTransactionHistory(int accountId, string accountName)
71139
{
72-
if (SelectedAccount == null || string.IsNullOrWhiteSpace(SelectedAccount.Name)) return;
140+
var transactions = await _apiService.GetAsync<List<TransactionDto>>($"Transactions/account-id/{accountId}");
73141

74-
if (Enum.TryParse(SelectedAccount.Currency, out BaseCurrencyType currency))
142+
if (transactions == null || !transactions.Any())
75143
{
76-
_logger.LogInformation("Seçilen para birimi: {Currency}", currency);
77-
}
78-
else
79-
{
80-
_logger.LogWarning("Geçersiz para birimi: {Currency}", SelectedAccount.Currency);
144+
Series = new ISeries[0];
145+
Title = new LabelVisual
146+
{
147+
Text = "Veri yok.",
148+
TextSize = 18,
149+
Paint = new SolidColorPaint(SKColors.Gray),
150+
Padding = new LiveChartsCore.Drawing.Padding(15)
151+
};
81152
return;
82153
}
83154

155+
var incomePoints = transactions
156+
.Where(t => t.Category.Type == TransactionType.Income)
157+
.Select(t => new DateTimePoint(t.TransactionDateUtc, (double)t.Amount))
158+
.ToList();
159+
160+
var expensePoints = transactions
161+
.Where(t => t.Category.Type == TransactionType.Expense)
162+
.Select(t => new DateTimePoint(t.TransactionDateUtc, (double)t.Amount))
163+
.ToList();
164+
165+
Series = new ISeries[]
166+
{
167+
new LineSeries<DateTimePoint>
168+
{
169+
Name = "Gelir",
170+
Values = incomePoints,
171+
Stroke = new SolidColorPaint(SKColors.MediumSpringGreen) { StrokeThickness = 2 },
172+
GeometryStroke = new SolidColorPaint(SKColors.MediumSpringGreen) { StrokeThickness = 4 },
173+
Fill = new LinearGradientPaint(SKColors.MediumSpringGreen.WithAlpha(90), SKColors.MediumSpringGreen.WithAlpha(10), new SKPoint(0.5f, 0), new SKPoint(0.5f, 1))
174+
},
175+
new LineSeries<DateTimePoint>
176+
{
177+
Name = "Gider",
178+
Values = expensePoints,
179+
Stroke = new SolidColorPaint(SKColors.IndianRed) { StrokeThickness = 2 },
180+
GeometryStroke = new SolidColorPaint(SKColors.IndianRed) { StrokeThickness = 4 },
181+
Fill = new LinearGradientPaint(SKColors.IndianRed.WithAlpha(90), SKColors.IndianRed.WithAlpha(10), new SKPoint(0.5f, 0), new SKPoint(0.5f, 1))
182+
}
183+
};
184+
185+
Title = new LabelVisual
186+
{
187+
Text = $"{accountName} - Hareketler",
188+
TextSize = 16,
189+
Paint = new SolidColorPaint(SKColors.WhiteSmoke),
190+
Padding = new LiveChartsCore.Drawing.Padding(15)
191+
};
192+
193+
XAxes = new Axis[]
194+
{
195+
new Axis
196+
{
197+
Labeler = value =>
198+
{
199+
try
200+
{
201+
var ticks = (long)value;
202+
if (ticks >= DateTime.MinValue.Ticks && ticks <= DateTime.MaxValue.Ticks)
203+
{
204+
return new DateTime(ticks).ToString("dd MMM");
205+
}
206+
return string.Empty;
207+
}
208+
catch
209+
{
210+
return string.Empty;
211+
}
212+
}
213+
,
214+
LabelsPaint = new SolidColorPaint(SKColors.LightGray),
215+
UnitWidth = TimeSpan.FromDays(1).Ticks,
216+
MinStep = TimeSpan.FromDays(1).Ticks
217+
218+
}
219+
};
220+
221+
YAxes = new Axis[]
222+
{
223+
new Axis
224+
{
225+
Labeler = value => value.ToString("C0"),
226+
LabelsPaint = new SolidColorPaint(SKColors.LightGray),
227+
SeparatorsPaint = new SolidColorPaint(SKColors.Gray) { StrokeThickness = 0.5f }
228+
}
229+
};
230+
}
231+
232+
[RelayCommand]
233+
private async Task SaveAccount()
234+
{
235+
if (SelectedAccount == null || string.IsNullOrWhiteSpace(SelectedAccount.Name)) return;
236+
84237
if (IsEditing)
85238
{
86239
var existingAccount = Accounts.FirstOrDefault(a => a.Id == SelectedAccount.Id);
87240
if (existingAccount != null)
88241
{
89242
existingAccount.Name = SelectedAccount.Name;
90243
existingAccount.Type = SelectedAccount.Type;
91-
existingAccount.Balance = SelectedAccount.Balance;
92244
existingAccount.Currency = SelectedAccount.Currency;
93245

94246
await _apiService.PutAsync<object>($"Account/{SelectedAccount.Id}", new AccountUpdateDto
95247
{
96248
Name = SelectedAccount.Name,
97249
Type = SelectedAccount.Type,
98-
Balance = SelectedAccount.Balance,
99-
Currency = currency
250+
Currency = SelectedAccount.Currency
100251
});
101252
}
102253
}
@@ -107,8 +258,7 @@ private async Task SaveAccount()
107258
Name = SelectedAccount.Name,
108259
Type = SelectedAccount.Type,
109260
IsActive = true,
110-
Balance = SelectedAccount.Balance,
111-
Currency = currency,
261+
Currency = SelectedAccount.Currency,
112262
});
113263

114264
SelectedAccount.Id = newAccount.Id;

0 commit comments

Comments
 (0)