Files
Blueberry/Blueberry/IssueWindow.xaml.cs
2025-12-17 12:23:29 +01:00

287 lines
11 KiB
C#

using Blueberry.Redmine;
using Blueberry.Redmine.Dto;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using Windows.Networking.NetworkOperators;
using Wpf.Ui.Controls;
namespace Blueberry
{
/// <summary>
/// Interaction logic for IssueWindow.xaml
/// </summary>
public partial class IssueWindow : FluentWindow
{
private DetailedIssue.Issue _issue;
private readonly RedmineManager _manager;
private readonly RedmineConfig _config;
private readonly List<JournalDisplay> _journalDisplays = [];
private CancellationTokenSource _tokenSource = new();
public ObservableCollection<JournalDisplay> Journals { get; set; } = [];
public IssueWindow(DetailedIssue.Issue issue, RedmineManager manager, RedmineConfig config)
{
_issue = issue;
_manager = manager;
_config = config;
InitializeComponent();
DataContext = this;
}
private async void FluentWindow_Loaded(object sender, RoutedEventArgs e)
{
iSubjectTextBox.Text = _issue.Subject;
iProjectTextBox.Text = _issue.Project.Name;
iDescriptionTextBox.Text = _issue.Description;
iCreatedTextBox.Text = _issue.CreatedOn.ToString("yyyy-MM-dd");
iUpdatedTextBox.Text = _issue.UpdatedOn.ToString("yyyy-MM-dd");
iSpentTimeTextBox.Text = _issue.SpentHours.ToString();
await DownloadJournals();
}
private async Task DownloadJournals()
{
Journals.Clear();
journalProgressRing.Visibility = Visibility.Visible;
List<TimeOnIssue.TimeEntry> hours = [];
try
{
var id = _issue.Id;
var newIssue = await _manager.GetIssueAsync(id);
if(newIssue != null)
_issue = newIssue;
hours = await _manager.GetTimeOnIssue(_issue.Id, progress: UpdateProgress(), token: _tokenSource.Token);
}
catch { }
_journalDisplays.Clear();
_journalDisplays.AddRange(await ProcessJournal(_issue.Journals, hours));
if (!_journalDisplays.Any(x => !x.IsData))
detailsToggleSwitch.IsChecked = true;
await LoadJournal();
journalProgressRing.Visibility = Visibility.Hidden;
}
private IProgress<(int, int)> UpdateProgress()
{
var p = new Progress<(int current, int total)>((x) =>
{
Dispatcher.Invoke(() =>
{
journalProgressRing.IsIndeterminate = false;
var percent = (int)Math.Round((double)x.current / x.total * 100);
journalProgressRing.Progress = percent;
});
});
return p;
}
private async Task LoadJournal()
{
var showDetails = detailsToggleSwitch.IsChecked ?? true;
Journals.Clear();
foreach (var j in _journalDisplays)
if ((!showDetails && !j.IsData)
|| showDetails)
Journals.Add(j);
}
private async void detailsToggleSwitch_Checked(object sender, RoutedEventArgs e)
{
await LoadJournal();
}
private void openBrowserButton_Click(object sender, RoutedEventArgs e)
{
string url = $"{_config.RedmineUrl}/issues/{_issue.Id}";
var psi = new ProcessStartInfo
{
FileName = url,
UseShellExecute = true
};
Process.Start(psi);
}
private void FluentWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
_tokenSource.Cancel();
}
private async void commentButton_Click(object sender, RoutedEventArgs e)
{
var comment = commentTextBox.Text;
if (string.IsNullOrWhiteSpace(comment))
return;
var isPrivate = privateToggle.IsChecked ?? false;
await _manager.AddComment(_issue.Id, comment, isPrivate);
commentTextBox.Text = "";
await DownloadJournals();
}
}
public partial class IssueWindow
{
public async Task<List<JournalDisplay>> ProcessJournal(IEnumerable<DetailedIssue.Journal> journals, List<TimeOnIssue.TimeEntry> hours)
{
var js = new List<JournalDisplay>();
foreach (var item in journals)
{
var user = item.User.Name;
var date = item.CreatedOn.ToString("yyyy-MM-dd HH:mm");
var content = item.Notes;
if (!string.IsNullOrWhiteSpace(content))
{
if (item.PrivateNotes)
js.Add(new JournalDisplay
{
User = user,
Date = date,
Content = content,
IsPrivate = true
});
else
js.Add(new JournalDisplay
{
User = user,
Date = date,
Content = content
});
}
foreach (var data in item.Details)
{
try
{
if (data.Property == "attr")
{
switch (data.Name)
{
case "due_date":
var old = data.OldValue ?? "null";
content = $"Due date:\n{old} > {data.NewValue}";
js.Add(new JournalDisplay
{
User = user,
Date = date,
Content = content,
IsData = true
});
break;
case "assigned_to_id":
old = "null";
if (data.OldValue != null)
{
var u = await _manager.GetUserAsync(int.Parse(data.OldValue));
old = u.Firstname + " " + u.Lastname;
}
var newU = await _manager.GetUserAsync(int.Parse(data.NewValue));
content = $"Assigned to:\n{old} > {newU.Firstname} {newU.Lastname}";
js.Add(new JournalDisplay
{
User = user,
Date = date,
Content = content,
IsData = true
});
break;
case "status_id":
old = "null";
if (data.OldValue != null)
{
old = (await _manager.GetStatusesAsync()).Where(x => x.Id == int.Parse(data.OldValue)).First().Name;
}
var newS = (await _manager.GetStatusesAsync()).Where(x => x.Id == int.Parse(data.NewValue)).First().Name;
content = $"Status changed to:\n{old} > {newS}";
js.Add(new JournalDisplay
{
User = user,
Date = date,
Content = content,
IsData = true
});
break;
default:
break;
}
}
if (data.Property == "cf")
{
try
{
if (int.TryParse(data.Name, out var cfId))
{
var cfs = await _manager.GetCustomFieldsAsync();
var cfName = cfs.Where(x => x.Id == cfId).First().Name;
var old = data.OldValue ?? "null";
content = $"{cfName}\n{old} > {data.NewValue}";
js.Add(new JournalDisplay
{
User = user,
Date = date,
Content = content,
IsData = true
});
}
}
catch (Exception) { }
}
} catch (Exception) { }
}
}
var totalHours = 0d;
hours = [.. hours.OrderBy(x => x.CreatedOn)];
foreach (var hour in hours)
{
totalHours += hour.Hours;
var user = hour.User.Name;
var date = hour.CreatedOn.ToString("yyyy-MM-dd HH:mm");
var content = $"Idő: {hour.Hours}\nEddig összesen: {totalHours}\n{hour.Comments}";
js.Add(new JournalDisplay
{
User = user,
Date = date,
Content = content,
IsData = true
});
}
js = [.. js.OrderBy(x => x.Date)];
return js;
}
public class JournalDisplay
{
public string User { get; set; }
public string Date { get; set; }
public string Content { get; set; }
public bool IsPrivate { get; set; }
public bool IsData { get; set; }
public Brush StatusColor
{
get
{
var resourceKey = IsData ? "TextFillColorTertiaryBrush" : "TextFillColorSecondaryBrush" ;
// Look up the brush from the App resources
return Application.Current.TryFindResource(resourceKey) as Brush;
}
}
public Visibility LockVisibility => IsPrivate ? Visibility.Visible : Visibility.Hidden;
public SolidColorBrush NameColor => StringToColorConverter.GetColorFromName(User);
}
}
}