Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions FinTrack/Enums/FeedbackTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.ComponentModel;

namespace FinTrack.Enums
{
public enum FeedbackTypes
{
[Description("Bug Report")]
BugReport,

[Description("Feature Request")]
FeatureRequest,

[Description("General Feedback")]
GeneralFeedback,

[Description("Other")]
Other
}
}
10 changes: 10 additions & 0 deletions FinTrack/Enums/NotificationType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace FinTrack.Enums
{
public enum NotificationType
{
Suggestion,
GoalAchieved,
Warning,
General
}
}
44 changes: 44 additions & 0 deletions FinTrack/Helpers/EnumToDescriptionConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace FinTrack.Helpers
{
public class EnumToDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not Enum enumValue)
return value;

return GetEnumDescription(enumValue);
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string description)
{
foreach (Enum enumValue in Enum.GetValues(targetType))
{
if (GetEnumDescription(enumValue) == description)
{
return enumValue;
}
}
}

return DependencyProperty.UnsetValue;
}

private string GetEnumDescription(Enum enumObj)
{
var fieldInfo = enumObj.GetType().GetField(enumObj.ToString());
if (fieldInfo == null) return enumObj.ToString();

var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

return attributes.Length > 0 ? attributes[0].Description : enumObj.ToString();
}
}
}
35 changes: 35 additions & 0 deletions FinTrack/Models/Notification/NotificationModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using CommunityToolkit.Mvvm.ComponentModel;
using FinTrack.Enums;

namespace FinTrack.Models.Notification
{
public partial class NotificationModel : ObservableObject
{
[ObservableProperty]
private string title = string.Empty;

[ObservableProperty]
private string message = string.Empty;

[ObservableProperty]
private string timestamp = string.Empty;

[ObservableProperty]
private NotificationType type;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(isUnread))]
private bool isRead = false;

public bool isUnread => !IsRead;

public NotificationModel(string title, string message, string? timestamp, NotificationType type, bool _isRead = false)
{
Title = title;
Message = message;
Type = type;
Timestamp = timestamp ?? DateTime.Now.ToString();
isRead = _isRead;
}
}
}
8 changes: 0 additions & 8 deletions FinTrack/Models/NotificationModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,4 @@ public class NotificationModel
[Column("IsRead")]
public bool IsRead { get; set; } = false;
}

public enum NotificationType
{
Info,
Warning,
Error,
Success,
}
}
100 changes: 100 additions & 0 deletions FinTrack/ViewModels/FeedbackViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,108 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FinTrack.Enums;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using System.Diagnostics;
using System.Windows;

namespace FinTrack.ViewModels
{
public partial class FeedbackViewModel : ObservableObject
{
[ObservableProperty]
private string? inputSubject;

[ObservableProperty]
private FeedbackTypes selectedFeedbackType;

[ObservableProperty]
private string? inputDescription;

[ObservableProperty]
private string? selectedFilePath;

public IEnumerable<FeedbackTypes> FeedbackTypes { get; }

private readonly ILogger<FeedbackViewModel> _logger;

public FeedbackViewModel(ILogger<FeedbackViewModel> logger)
{
_logger = logger;

FeedbackTypes = Enum.GetValues(typeof(FeedbackTypes)).Cast<FeedbackTypes>();
SelectedFeedbackType = FeedbackTypes.FirstOrDefault();
}

[RelayCommand(CanExecute = nameof(CanSendFeedback))]
private void SendFeedback()
{
string subject = InputSubject ?? "The title has been left blank.";
string description = InputDescription ?? "The description has been left blank.";
string filePath = SelectedFilePath ?? "No file selected.";
string feedbackType = SelectedFeedbackType.ToString();

string feedbackMessage = $"Subject: {subject}\n" +
$"Type: {feedbackType}\n" +
$"Description: {description}\n" +
$"File Path: {filePath}";

// TODO: feedbackMessage should be sent to a server or an email...

MessageBox.Show(feedbackMessage, "Feedback Submitted", MessageBoxButton.OK, MessageBoxImage.Information);
_logger.LogInformation("Feedback submitted: Subject: {Subject}, Type: {Type}, Description: {Description}, File Path: {FilePath}",
subject, feedbackType, description, filePath);

ClearForm();
}

[RelayCommand]
private void BrowseFile()
{
var openFileDialog = new OpenFileDialog
{
Title = "Select a file to attach",
Filter = "Image Files (*.png;*.jpg;*.jpeg)|*.png;*.jpg;*.jpeg|All Files (*.*)|*.*"
};

if (openFileDialog.ShowDialog() == true)
{
SelectedFilePath = openFileDialog.FileName;
_logger.LogInformation("Seçilen dosya: {FilePath}", SelectedFilePath);
Copy link

Copilot AI Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This log message is in Turkish. For consistency with the rest of the code and UI, translate it to English, e.g., 'Selected file: {FilePath}'.

Suggested change
_logger.LogInformation("Seçilen dosya: {FilePath}", SelectedFilePath);
_logger.LogInformation("Selected file: {FilePath}", SelectedFilePath);

Copilot uses AI. Check for mistakes.
}
}

[RelayCommand]
private void OpenLink(string? url)
{
if (string.IsNullOrEmpty(url) || url == "#") return;

try
{
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
}
catch (Exception ex)
{
MessageBox.Show($"Link açılamadı: {ex.Message}", "Hata", MessageBoxButton.OK, MessageBoxImage.Error);
_logger.LogError(ex, "Link açma hatası: {Url}", url);
}
}

private bool CanSendFeedback()
{
return !string.IsNullOrWhiteSpace(InputSubject) &&
!string.IsNullOrWhiteSpace(InputDescription);
}

partial void OnInputSubjectChanged(string? value) => SendFeedbackCommand.NotifyCanExecuteChanged();
partial void OnInputDescriptionChanged(string? value) => SendFeedbackCommand.NotifyCanExecuteChanged();

private void ClearForm()
{
InputSubject = string.Empty;
InputDescription = string.Empty;
SelectedFilePath = null;
SelectedFeedbackType = FeedbackTypes.FirstOrDefault();
}
}
}
88 changes: 88 additions & 0 deletions FinTrack/ViewModels/NotificationViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,96 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FinTrack.Enums;
using FinTrack.Models.Notification;
using Microsoft.Extensions.Logging;
using System.Collections.ObjectModel;

namespace FinTrack.ViewModels
{
public partial class NotificationViewModel : ObservableObject
{
[ObservableProperty]
private ObservableCollection<NotificationModel> notifications;

private readonly ILogger<NotificationViewModel> _logger;

public NotificationViewModel(ILogger<NotificationViewModel> logger)
{
_logger = logger;
LoadSampleNotifications();
}

// TODO: [TEST]
private void LoadSampleNotifications()
{
Notifications = new ObservableCollection<NotificationModel>
{
new NotificationModel
(
"Yeni Bütçe Önerisi",
"Aylık 'Eğlence' harcamalarınız için yeni bir bütçe limiti önerimiz var. Göz atmak için tıklayın.",
"2 saat önce",
NotificationType.Suggestion,
false
),
new NotificationModel
(
"Hedefe Ulaşıldı!",
"'Yeni Bilgisayar' birikim hedefinize ulaştınız. Tebrikler!",
"1 gün önce",
NotificationType.GoalAchieved,
true
),
new NotificationModel
(
"Fatura Hatırlatması",
"İnternet faturanızın son ödeme tarihi yarın. Gecikme faizinden kaçınmak için ödeme yapın.",
"3 gün önce",
NotificationType.Warning,
false
)
};
}

[RelayCommand]
private void MarkAllAsRead()
{
foreach (var notification in Notifications)
{
if (notification.isUnread)
{
notification.IsRead = true;
_logger.LogInformation($"Notification '{notification.Title}' marked as read.");
Copy link

Copilot AI Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid string interpolation in logging to preserve structured logging. Use placeholder syntax, e.g., _logger.LogInformation("Notification '{Title}' marked as read.", notification.Title);

Suggested change
_logger.LogInformation($"Notification '{notification.Title}' marked as read.");
_logger.LogInformation("Notification '{Title}' marked as read.", notification.Title);

Copilot uses AI. Check for mistakes.
}
}
_logger.LogInformation("All notifications marked as read.");
}

[RelayCommand]
private void ClearAll()
{
Notifications.Clear();
_logger.LogInformation("All notifications cleared.");
}

[RelayCommand]
private void MarkAsRead(NotificationModel? notification)
{
if (notification.isUnread)
{
notification.IsRead = true;
_logger.LogInformation($"Notification '{notification.Title}' marked as read.");
}
}

[RelayCommand]
private void DeleteNotification(NotificationModel? notification)
{
if (notification != null)
{
Notifications.Remove(notification);
_logger.LogInformation($"Notification '{notification.Title}' deleted.");
}
}
}
}
Loading
Loading