252 lines
9.3 KiB
C#
252 lines
9.3 KiB
C#
using Blueberry.Redmine;
|
|
using Blueberry.Redmine.Dto;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.ObjectModel;
|
|
using System.Windows;
|
|
using Wpf.Ui.Controls;
|
|
|
|
namespace Blueberry
|
|
{
|
|
/// <summary>
|
|
/// Interaction logic for HoursWindow.xaml
|
|
/// </summary>
|
|
public partial class HoursWindow : FluentWindow
|
|
{
|
|
private readonly List<UserInfo.User> _users = [];
|
|
private readonly RedmineManager _manager;
|
|
private readonly RedmineConfig _config;
|
|
private readonly ConcurrentDictionary<int, string> _issueNames = [];
|
|
|
|
public ObservableCollection<DisplayHours> Hours { get; set; } = [];
|
|
|
|
public HoursWindow(RedmineManager manager, RedmineConfig config)
|
|
{
|
|
InitializeComponent();
|
|
DataContext = this;
|
|
_manager = manager;
|
|
_config = config;
|
|
}
|
|
|
|
private async void FluentWindow_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
userComboBox.IsEnabled =
|
|
searchButton.IsEnabled =
|
|
dateButton.IsEnabled = false;
|
|
var u = await _manager.GetUsersAsync(progress: UpdateProgress());
|
|
var current = await _manager.GetCurrentUserAsync();
|
|
hoursProgress.Visibility = Visibility.Hidden;
|
|
hoursProgress.IsIndeterminate = true;
|
|
_users.Clear();
|
|
_users.AddRange(u);
|
|
userComboBox.Items.Clear();
|
|
foreach (var user in u)
|
|
userComboBox.Items.Add(user);
|
|
|
|
userComboBox.SelectedItem = current;
|
|
userComboBox.IsEnabled =
|
|
searchButton.IsEnabled =
|
|
dateButton.IsEnabled = true;
|
|
}
|
|
|
|
private IProgress<(int, int)> UpdateProgress()
|
|
{
|
|
var p = new Progress<(int current, int total)>((x) =>
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
hoursProgress.IsIndeterminate = false;
|
|
var percent = (int)((double)x.current / x.total * 100);
|
|
hoursProgress.Progress = percent;
|
|
});
|
|
});
|
|
return p;
|
|
}
|
|
|
|
private void dateButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
calendarFlyout.IsOpen = true;
|
|
}
|
|
|
|
private void UpdateProgressI()
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
var percent = (int)((double)loadI / totalI * 100);
|
|
lock (_lock)
|
|
{
|
|
hoursProgress.Progress = percent;
|
|
}
|
|
});
|
|
}
|
|
|
|
private int loadInternalI = 0;
|
|
private object _lock = new();
|
|
private int loadI { get
|
|
{
|
|
var result = 0;
|
|
lock(_lock)
|
|
{
|
|
result = loadInternalI;
|
|
}
|
|
return result;
|
|
}
|
|
set
|
|
{
|
|
lock (_lock)
|
|
{
|
|
loadInternalI = value;
|
|
}
|
|
}
|
|
}
|
|
private int totalI = 0;
|
|
private async void searchButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var user = userComboBox.SelectedItem as UserInfo.User;
|
|
if(user is null)
|
|
return;
|
|
|
|
var selectedDates = userCalendar.SelectedDates;
|
|
if (selectedDates.Count == 0)
|
|
return;
|
|
|
|
ConcurrentBag<DisplayHours> hours = [];
|
|
Task[] tasks = new Task[selectedDates.Count];
|
|
Hours.Clear();
|
|
hoursProgress.Visibility= Visibility.Visible;
|
|
|
|
for (int i = 0; i < selectedDates.Count; i++)
|
|
{
|
|
hoursProgress.IsIndeterminate = false;
|
|
totalI = selectedDates.Count;
|
|
var date = selectedDates[i];
|
|
tasks[i] = Task.Run(async () =>
|
|
{
|
|
var h = await _manager.GetTimeForUserAsync(user.Id, date, date);
|
|
foreach (var item in h)
|
|
{
|
|
var dh = new DisplayHours()
|
|
{
|
|
ProjectName = item.Project.Name,
|
|
IssueName = await GetIssueNameAsync(item.Issue.Id),
|
|
IssueId = item.Issue.Id.ToString(),
|
|
Date = date,
|
|
CreatedOn = item.CreatedOn,
|
|
Hours = item.Hours.ToString(),
|
|
Comments = item.Comments,
|
|
IsSeparator = false
|
|
};
|
|
hours.Add(dh);
|
|
}
|
|
loadI++;
|
|
UpdateProgressI();
|
|
});
|
|
await Task.Delay(10);
|
|
}
|
|
|
|
await Task.WhenAll(tasks);
|
|
|
|
hoursProgress.IsIndeterminate = true;
|
|
|
|
var newTickets = await _manager.GetIssuesAsync(user.Id, createdFrom: selectedDates.First(), createdTo: selectedDates.Last());
|
|
var closedTickets = await _manager.GetIssuesAsync(user.Id, isOpen: false, updatedFrom: selectedDates.First(), updatedTo: selectedDates.Last());
|
|
var currentTickets = await _manager.GetUserOpenIssuesAsync(user.Id);
|
|
|
|
var total = hours.Sum(x => double.Parse(x.Hours));
|
|
totalHoursTextBlock.Text = total.ToString();
|
|
avgDayTextBlock.Text = Math.Round(total / selectedDates.Count, 2).ToString();
|
|
|
|
int workingDays = 0;
|
|
foreach (var day in selectedDates)
|
|
{
|
|
if(day.DayOfWeek != DayOfWeek.Saturday &&
|
|
day.DayOfWeek != DayOfWeek.Sunday)
|
|
workingDays++;
|
|
}
|
|
avgWorkdayTextBlock.Text = Math.Round(total / workingDays, 2).ToString();
|
|
|
|
newTicketsTextBlock.Text = newTickets.Count.ToString();
|
|
closedTicketsTextBlock.Text = closedTickets.Count.ToString();
|
|
|
|
minTicketAgeTextBlock.Text = Math.Round(currentTickets.Min(x => (DateTime.Now - x.CreatedOn).TotalDays), 2) + " nap";
|
|
maxTicketAgeTextBlock.Text = Math.Round(currentTickets.Max(x => (DateTime.Now - x.CreatedOn).TotalDays), 2) + " nap";
|
|
avgTicketAgeTextBlock.Text = Math.Round(currentTickets.Average(x => (DateTime.Now - x.CreatedOn).TotalDays), 2) + " nap";
|
|
var ages = currentTickets.Select(x => (DateTime.Now - x.CreatedOn).TotalDays).Order().ToList();
|
|
medianTicketAgeTextBlock.Text = Math.Round(ages[ages.Count / 2], 2) + " nap";
|
|
|
|
var orderedHours = hours.OrderByDescending(h => h.CreatedOn).OrderBy(h => h.Date).ToList();
|
|
var previousDate = DateTime.MinValue;
|
|
for (int i = 0; i < orderedHours.Count; i++)
|
|
{
|
|
if(orderedHours[i].Date.Date > previousDate)
|
|
{
|
|
total = orderedHours.Where(x => x.Date.Date == orderedHours[i].Date.Date).Sum(x => double.Parse(x.Hours));
|
|
var dh = new DisplayHours()
|
|
{
|
|
IsSeparator = true,
|
|
SeparatorText = orderedHours[i].Date.ToString("yyyy-MM-dd") + " | Összesen: " + total + " óra"
|
|
};
|
|
orderedHours.Insert(i, dh);
|
|
previousDate = orderedHours[i+1].Date.Date;
|
|
}
|
|
}
|
|
|
|
hoursProgress.Visibility = Visibility.Hidden;
|
|
|
|
foreach (var item in orderedHours)
|
|
Hours.Add(item);
|
|
}
|
|
|
|
private async Task<string> GetIssueNameAsync(int issueId)
|
|
{
|
|
try
|
|
{
|
|
if (_issueNames.ContainsKey(issueId))
|
|
return _issueNames[issueId];
|
|
|
|
var name = (await _manager.GetSimpleIssueAsync(issueId)).Subject;
|
|
_issueNames.TryAdd(issueId, name);
|
|
return name;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(ex.Message);
|
|
Console.WriteLine(issueId);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public class DisplayHours
|
|
{
|
|
public string ProjectName { get; set; } = "";
|
|
public string IssueName { get; set; } = "";
|
|
public string IssueId { get; set; } = "";
|
|
public DateTime Date { get; set; }
|
|
public DateTime CreatedOn { get; set; }
|
|
public string Hours { get; set; } = "";
|
|
public string Comments { get; set; } = "";
|
|
public string SeparatorText { get; set; } = "";
|
|
public bool IsSeparator { get; set; }
|
|
public Visibility CardVisibility => IsSeparator ? Visibility.Visible : Visibility.Hidden;
|
|
public Visibility ButtonVisibility => IsSeparator ? Visibility.Hidden : Visibility.Visible;
|
|
}
|
|
|
|
private async void openIssueButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (sender is FrameworkElement button && button.DataContext is DisplayHours item)
|
|
{
|
|
var issueId = int.Parse(item.IssueId);
|
|
|
|
try
|
|
{
|
|
var issue = await _manager.GetIssueAsync(issueId);
|
|
var issueWindow = new IssueWindow(issue, _manager, _config);
|
|
issueWindow.Show();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|