Skip to content

Commit

Permalink
Add export grouping of tasks, Redesigned Export page
Browse files Browse the repository at this point in the history
  • Loading branch information
popcatalin81 committed May 12, 2020
1 parent 76a7ccb commit 4f788d5
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 57 deletions.
4 changes: 1 addition & 3 deletions QBTracker/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme BaseTheme="Dark" PrimaryColor="Cyan" SecondaryColor="LightGreen" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary Source="Resources/Resources.xaml"/>
<ResourceDictionary Source="Resources/QBCalendarStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"></converters:BoolToVisibilityConverter>
<converters:InverterConverter x:Key="InverterConverter" />
<converters:MaterialColorConverter x:Key="MaterialColorConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>
42 changes: 42 additions & 0 deletions QBTracker/Converters/EnumHumanizerConverter.cs
Original file line number Diff line number Diff line change
@@ -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<Enum>), typeof(IEnumerable<ValueDescription>))]
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<object>()
.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; }
}
}
28 changes: 23 additions & 5 deletions QBTracker/Model/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
4 changes: 2 additions & 2 deletions QBTracker/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
8 changes: 8 additions & 0 deletions QBTracker/Resources/Resources.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:QBTracker.Converters">
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:InverterConverter x:Key="InverterConverter" />
<converters:MaterialColorConverter x:Key="MaterialColorConverter" />
<converters:EnumHumanizerConverter x:Key="EnumHumanizerConverter" />
</ResourceDictionary>
80 changes: 67 additions & 13 deletions QBTracker/ViewModels/ExportViewModel.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -48,6 +52,15 @@ public DateTime EndDate
}
}

public IEnumerable<RoundingInterval> RoundingIntervals =>
(RoundingInterval[]) Enum.GetValues(typeof(RoundingInterval));

public IEnumerable<RoundingType> RoundingTypes =>
(RoundingType[])Enum.GetValues(typeof(RoundingType));

public IEnumerable<GroupingType> GroupingTypes =>
(GroupingType[])Enum.GetValues(typeof(GroupingType));

public RelayCommand ExportCommand { get; }
public RelayCommand GoBack { get; }
public Settings ExportSettings { get; }
Expand Down Expand Up @@ -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);
Expand All @@ -121,13 +130,50 @@ private void ExportData(string file)
}
}

private IEnumerable<ExportRecord> Group(IEnumerable<TimeRecord> 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)
{
Expand All @@ -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; }
}
}
}
71 changes: 37 additions & 34 deletions QBTracker/Views/ExportView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@
<RowDefinition Height="60" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition />
</Grid.ColumnDefinitions>

<Border Grid.Row="0" Grid.Column="0" BorderBrush="{DynamicResource MaterialDesignDivider}" BorderThickness="0 0 0 1">
<Border Grid.Row="0" Grid.ColumnSpan="2" BorderBrush="{DynamicResource MaterialDesignDivider}" BorderThickness="0 0 0 1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
Expand Down Expand Up @@ -56,43 +60,42 @@
materialDesign:HintAssist.Hint="Start date"
SelectedDate="{Binding StartDate}"
Style="{StaticResource MaterialDesignFloatingHintDatePicker}" />
<DatePicker Grid.Row="1" Margin="10,0,10,0"
<DatePicker Grid.Row="1" Grid.Column="1" Margin="10,0,10,0"
Width="140"
HorizontalAlignment="Right"
materialDesign:HintAssist.Hint="End date"
SelectedDate="{Binding EndDate}"
Style="{StaticResource MaterialDesignFloatingHintDatePicker}" />
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
Orientation="Horizontal">
<RadioButton
Style="{StaticResource MaterialDesignDarkRadioButton}"
Margin="10"
IsChecked="{Binding ExportSettings.NoRounding}"
Content="No rounding" />
<RadioButton
Style="{StaticResource MaterialDesignDarkRadioButton}"
Margin="10"
IsChecked="{Binding ExportSettings.Rounding15Min}"
Content="Round to 15 min" />
<RadioButton
Style="{StaticResource MaterialDesignDarkRadioButton}"
Margin="10"
IsChecked="{Binding ExportSettings.Rounding30Min}"
Content="Round to 30 min" />
</StackPanel>
<StackPanel Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"
Orientation="Horizontal"
IsEnabled="{Binding ExportSettings.NoRounding, Converter={StaticResource InverterConverter}}">
<RadioButton
Style="{StaticResource MaterialDesignDarkRadioButton}"
Margin="10"
IsChecked="{Binding ExportSettings.MidPointRounding}"
Content="Midpoint Rounding" />
<RadioButton
Style="{StaticResource MaterialDesignDarkRadioButton}"
Margin="10"
IsChecked="{Binding ExportSettings.CeilingRounding}"
Content="Ceiling Rounding" />
</StackPanel>
<TextBlock Text="Rounding Interval:" Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="10" HorizontalAlignment="Right" />
<ComboBox Grid.Row="2"
Grid.Column="1"
Margin="10, 0"
VerticalAlignment="Center"
materialDesign:HintAssist.Hint="Interval"
SelectedValuePath="Value"
DisplayMemberPath="Description"
ItemsSource="{Binding RoundingIntervals, Converter={StaticResource EnumHumanizerConverter}}"
SelectedValue="{Binding ExportSettings.RoundingInterval}"/>
<TextBlock Text="Rounding Method:" Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10" HorizontalAlignment="Right" />
<ComboBox Grid.Row="3"
Grid.Column="1"
Margin="10, 0"
VerticalAlignment="Center"
materialDesign:HintAssist.Hint="Method"
SelectedValuePath="Value"
DisplayMemberPath="Description"
ItemsSource="{Binding RoundingTypes, Converter={StaticResource EnumHumanizerConverter}}"
SelectedValue="{Binding ExportSettings.RoundingType}"/>
<TextBlock Text="Task Grouping:" Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="10" HorizontalAlignment="Right" />
<ComboBox Grid.Row="4"
Grid.Column="1"
Margin="10, 0"
VerticalAlignment="Center"
materialDesign:HintAssist.Hint="Grouping"
SelectedValuePath="Value"
DisplayMemberPath="Description"
ItemsSource="{Binding GroupingTypes, Converter={StaticResource EnumHumanizerConverter}}"
SelectedValue="{Binding ExportSettings.GroupingType}"/>

</Grid>
</UserControl>

0 comments on commit 4f788d5

Please sign in to comment.