From 4f788d5d826ad322054c2b025359c56b44856376 Mon Sep 17 00:00:00 2001 From: Pop Catalin Date: Tue, 12 May 2020 14:35:12 +0300 Subject: [PATCH] Add export grouping of tasks, Redesigned Export page --- QBTracker/App.xaml | 4 +- .../Converters/EnumHumanizerConverter.cs | 42 ++++++++++ QBTracker/Model/Settings.cs | 28 +++++-- QBTracker/Properties/AssemblyInfo.cs | 4 +- QBTracker/Resources/Resources.xaml | 8 ++ QBTracker/ViewModels/ExportViewModel.cs | 80 ++++++++++++++++--- QBTracker/Views/ExportView.xaml | 71 ++++++++-------- 7 files changed, 180 insertions(+), 57 deletions(-) create mode 100644 QBTracker/Converters/EnumHumanizerConverter.cs create mode 100644 QBTracker/Resources/Resources.xaml diff --git a/QBTracker/App.xaml b/QBTracker/App.xaml index eb26edf..a8d590d 100644 --- a/QBTracker/App.xaml +++ b/QBTracker/App.xaml @@ -10,11 +10,9 @@ + - - - diff --git a/QBTracker/Converters/EnumHumanizerConverter.cs b/QBTracker/Converters/EnumHumanizerConverter.cs new file mode 100644 index 0000000..e0e0fec --- /dev/null +++ b/QBTracker/Converters/EnumHumanizerConverter.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Windows.Data; + +using Humanizer; + +namespace QBTracker.Converters +{ + [ValueConversion(typeof(IEnumerable), typeof(IEnumerable))] + public class EnumHumanizerConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + return null; + if (value is IEnumerable en) + return en.Cast() + .Select(x => new ValueDescription(x, x.ToString().Humanize())); + return new ValueDescription(value, value.ToString().Humanize()); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + + public class ValueDescription + { + public ValueDescription(object value, string description) + { + Value = value; + Description = description; + } + + public object Value { get; } + public string Description { get; } + } +} \ No newline at end of file diff --git a/QBTracker/Model/Settings.cs b/QBTracker/Model/Settings.cs index f7c9c57..d31c392 100644 --- a/QBTracker/Model/Settings.cs +++ b/QBTracker/Model/Settings.cs @@ -7,14 +7,32 @@ public class Settings public int Id { get; set; } public string ExportFolder { get; set; } public string ExportFileName { get; set; } - public bool NoRounding { get; set; } = true; - public bool Rounding15Min { get; set; } = true; - public bool Rounding30Min { get; set; } - public bool MidPointRounding { get; set; } = true; - public bool CeilingRounding { get; set; } + public RoundingInterval RoundingInterval { get; set; } = RoundingInterval.NoRounding; + public RoundingType RoundingType { get; set; } = RoundingType.MidPointRounding; + public GroupingType GroupingType { get; set; } = GroupingType.NoGrouping; public bool? IsDark { get; set; } public PrimaryColor? PrimaryColor { get; set; } public SecondaryColor? SecondaryColor { get; set; } public bool StartWithWindows { get; set; } } + + public enum RoundingInterval + { + NoRounding = 0, + RoundTo15Min, + RoundTo30Min, + } + + public enum RoundingType + { + MidPointRounding = 0, + CeilingRounding + } + + public enum GroupingType + { + NoGrouping = 0, + GroupBeforeRound, + GroupAfterRound, + } } diff --git a/QBTracker/Properties/AssemblyInfo.cs b/QBTracker/Properties/AssemblyInfo.cs index e37f3ab..f769a01 100644 --- a/QBTracker/Properties/AssemblyInfo.cs +++ b/QBTracker/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.5.0")] -[assembly: AssemblyFileVersion("1.0.5.0")] +[assembly: AssemblyVersion("1.0.6.0")] +[assembly: AssemblyFileVersion("1.0.6.0")] diff --git a/QBTracker/Resources/Resources.xaml b/QBTracker/Resources/Resources.xaml new file mode 100644 index 0000000..8d32346 --- /dev/null +++ b/QBTracker/Resources/Resources.xaml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/QBTracker/ViewModels/ExportViewModel.cs b/QBTracker/ViewModels/ExportViewModel.cs index d033a1a..3d1e0c9 100644 --- a/QBTracker/ViewModels/ExportViewModel.cs +++ b/QBTracker/ViewModels/ExportViewModel.cs @@ -1,6 +1,10 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Text.RegularExpressions; using MaterialDesignThemes.Wpf; using Microsoft.Win32; @@ -48,6 +52,15 @@ public DateTime EndDate } } + public IEnumerable RoundingIntervals => + (RoundingInterval[]) Enum.GetValues(typeof(RoundingInterval)); + + public IEnumerable RoundingTypes => + (RoundingType[])Enum.GetValues(typeof(RoundingType)); + + public IEnumerable GroupingTypes => + (GroupingType[])Enum.GetValues(typeof(GroupingType)); + public RelayCommand ExportCommand { get; } public RelayCommand GoBack { get; } public Settings ExportSettings { get; } @@ -92,19 +105,15 @@ private void ExportData(string file) int row = 2; while (date <= EndDate) { - foreach (var timeRecord in mainVm.Repository.GetTimeRecords(date)) + foreach (var exportRecord in Group(mainVm.Repository.GetTimeRecords(date))) { - if (timeRecord.EndTime == null) - continue; - var duration = (timeRecord.EndTime - timeRecord.StartTime).Value; - ws.Cells[$"A{row}"].Value = date; ws.Cells[$"A{row}"].Style.Numberformat.Format = "mm-dd-yy"; // In Excel speak this means Date field that will be displayed with Region format - ws.Cells[$"B{row}"].Value = timeRecord.ProjectName; - ws.Cells[$"C{row}"].Value = timeRecord.TaskName; - ws.Cells[$"D{row}"].Value = Round(duration.TotalHours); + ws.Cells[$"B{row}"].Value = exportRecord.ProjectName; + ws.Cells[$"C{row}"].Value = exportRecord.TaskName; + ws.Cells[$"D{row}"].Value = exportRecord.DurationHours; ws.Cells[$"D{row}"].Style.Numberformat.Format = "0.##"; - ws.Cells[$"E{row}"].Value = timeRecord.Notes; + ws.Cells[$"E{row}"].Value = exportRecord.Notes; row++; } date = date.AddDays(1); @@ -121,13 +130,50 @@ private void ExportData(string file) } } + private IEnumerable Group(IEnumerable timeRecords) + { + timeRecords = timeRecords.Where(x => x.EndTime != null); + return ExportSettings.GroupingType switch + { + GroupingType.NoGrouping => timeRecords.Select(x => new ExportRecord + { + ProjectName = x.ProjectName, + TaskName = x.TaskName, + Notes = x.Notes, + DurationHours = Round((x.EndTime.Value - x.StartTime).TotalHours) + }), + GroupingType.GroupBeforeRound => timeRecords + .GroupBy(x => (x.ProjectName, x.TaskName)) + .Select(g => new ExportRecord + { + ProjectName = g.Key.ProjectName, + TaskName = g.Key.TaskName, + DurationHours = Round(g.Sum(x => (x.EndTime.Value - x.StartTime).TotalHours)), + Notes = string.Join(Environment.NewLine, g.Select(x => x.Notes)) + }), + GroupingType.GroupAfterRound => timeRecords + .GroupBy(x => (x.ProjectName, x.TaskName)) + .Select(g => new ExportRecord + { + ProjectName = g.Key.ProjectName, + TaskName = g.Key.TaskName, + DurationHours = g.Sum(x => Round((x.EndTime.Value - x.StartTime).TotalHours)), + Notes = string.Join(Environment.NewLine, g.Select(x => x.Notes)) + }), + }; + } + private double Round(in double hours) { - if (ExportSettings.Rounding15Min) - return ExportSettings.MidPointRounding ? RoundF(hours, 4) : CeilingF(hours, 4); + if (ExportSettings.RoundingInterval == RoundingInterval.RoundTo15Min) + return ExportSettings.RoundingType == RoundingType.MidPointRounding + ? RoundF(hours, 4) + : CeilingF(hours, 4); - if (ExportSettings.Rounding30Min) - return ExportSettings.MidPointRounding ? RoundF(hours, 2) : CeilingF(hours, 2); + if (ExportSettings.RoundingInterval == RoundingInterval.RoundTo30Min) + return ExportSettings.RoundingType == RoundingType.MidPointRounding + ? RoundF(hours, 2) + : CeilingF(hours, 2); static double RoundF(in double hours, double factor) { @@ -146,5 +192,13 @@ static double CeilingF(in double hours, double factor) } return hours; } + + public class ExportRecord + { + public string ProjectName { get; set; } + public string TaskName { get; set; } + public string Notes { get; set; } + public double DurationHours { get; set; } + } } } \ No newline at end of file diff --git a/QBTracker/Views/ExportView.xaml b/QBTracker/Views/ExportView.xaml index fa5f180..32f2aa7 100644 --- a/QBTracker/Views/ExportView.xaml +++ b/QBTracker/Views/ExportView.xaml @@ -18,8 +18,12 @@ + + + + - + @@ -56,43 +60,42 @@ materialDesign:HintAssist.Hint="Start date" SelectedDate="{Binding StartDate}" Style="{StaticResource MaterialDesignFloatingHintDatePicker}" /> - - - - - - - - - - + + + + + + + \ No newline at end of file