Skip to content

Commit

Permalink
Add Month aggregate time display in calendar
Browse files Browse the repository at this point in the history
New Tray icon when recording
Ability to filter projects for Export
  • Loading branch information
popcatalin81 committed Jan 28, 2022
1 parent ed1efb0 commit 02f572d
Show file tree
Hide file tree
Showing 14 changed files with 271 additions and 26 deletions.
3 changes: 1 addition & 2 deletions QBTracker/Converters/DayFillConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ public DayFillConverter()
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var date = (DateTime)values[0];
var percent = Math.Min(Repository.GetHours(date), 8d) / 8d;
Repository.GetHours(date);
var percent = Math.Min(Repository.GetDayAggregatedDayTime(date).TotalHours, 8d) / 8d;
return percent * System.Convert.ToDouble(values[1]);
}

Expand Down
37 changes: 37 additions & 0 deletions QBTracker/Converters/MonthTimeConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using Humanizer;
using Humanizer.Localisation;
using QBTracker.DataAccess;

namespace QBTracker.Converters
{
internal class MonthTimeConverter : IMultiValueConverter
{
public MonthTimeConverter()
{
this.Repository = (IRepository)Application.Current.Resources["Repository"];
}

private IRepository Repository { get; }

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var date = (DateTime)values[0];
var timeAggregate = Repository.GetDayAggregatedMonthTime(date);
return timeAggregate.Humanize(2, maxUnit:TimeUnit.Hour, minUnit: TimeUnit.Minute);
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
}
}
8 changes: 5 additions & 3 deletions QBTracker/DataAccess/IRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ public interface IRepository : IDisposable
Task GetTaskById(int id);
void AddTask(Task task);
void UpdateTask(Task task);
List<TimeRecord> GetTimeRecords(DateTime date);
List<TimeRecord> GetTimeRecords(DateTime date, IReadOnlyCollection<int> projectIds = null);
TimeRecord GetRunningTimeRecord();
void AddTimeRecord(TimeRecord record);
void UpdateTimeRecord(TimeRecord record);
void DeleteTimeRecord(int timeRecordId);
void DeleteTimeRecord(TimeRecord record);
Settings GetSettings();
void UpdateSettings();
ILiteRepository GetLiteRepository();
double GetHours(DateTime date);
TimeSpan GetDayAggregatedDayTime(DateTime date);
TimeSpan GetDayAggregatedMonthTime(DateTime date);
string GetProjectInfo(int projectId);
}
}
89 changes: 77 additions & 12 deletions QBTracker/DataAccess/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,19 @@ public void UpdateTask(Task task)
Db.Update(task, "Tasks");
}

public List<TimeRecord> GetTimeRecords(DateTime date)
public List<TimeRecord> GetTimeRecords(DateTime date, IReadOnlyCollection<int> projectIds = null)
{
var start = date.Date;
var end = start.AddDays(1).AddTicks(-1);
return Db.Query<TimeRecord>("TimeRecords")
.Where(x => x.StartTime >= start && x.StartTime <= end)
.OrderBy(x => x.StartTime)
.ToList();

var q = Db.Query<TimeRecord>("TimeRecords")
.Where(x => x.StartTime >= start && x.StartTime <= end);

if (projectIds != null)
q = q.Where(x => projectIds.Contains(x.ProjectId));

return q.OrderBy(x => x.StartTime)
.ToList();
}

public TimeRecord GetRunningTimeRecord()
Expand All @@ -103,19 +108,25 @@ public TimeRecord GetRunningTimeRecord()
public void AddTimeRecord(TimeRecord record)
{
Db.Insert(record, "TimeRecords");
TimeUpdated += 1;
UpdateAggregatedTime(record.StartTime);
TimeUpdated++;
}

public void UpdateTimeRecord(TimeRecord record)
{
var old = Db.SingleOrDefault<TimeRecord>(x => x.Id == record.Id, "TimeRecords");
if (old != null && old.StartTime.Day != record.StartTime.Day)
UpdateAggregatedTime(old.StartTime);
Db.Update(record, "TimeRecords");
TimeUpdated += 1;
UpdateAggregatedTime(record.StartTime);
TimeUpdated++;
}

public void DeleteTimeRecord(int timeRecordId)
public void DeleteTimeRecord(TimeRecord record)
{
Db.Delete<TimeRecord>(timeRecordId, "TimeRecords");
TimeUpdated += 1;
Db.Delete<TimeRecord>(record.Id, "TimeRecords");
UpdateAggregatedTime(record.StartTime);
TimeUpdated++;
}

public Settings GetSettings()
Expand Down Expand Up @@ -144,24 +155,78 @@ public ILiteRepository GetLiteRepository()
return Db;
}

public double GetHours(DateTime date)
public TimeSpan GetDayAggregatedDayTime(DateTime date)
{
var rec = GetTimeRecords(date);
return rec.Sum(x => Math.Abs(((x.EndTime ?? DateTime.Now) - x.StartTime).TotalHours));
return rec.Select(x => (x.EndTime ?? DateTime.Now) - x.StartTime).Aggregate(TimeSpan.Zero, (acc, x) => acc + x);
}

public TimeSpan GetDayAggregatedMonthTime(DateTime date)
{
return GetMonthAggregate(date).AggregateTime;
}

public string GetProjectInfo(int projectId)
{
return $"Tasks: {Db.Query<Task>("Tasks").Where(x => x.ProjectId == projectId && !x.IsDeleted).Count()}";
}

public void Dispose()
{
Db?.Dispose();
}

private void UpdateAggregatedTime(DateTime day)
{
var aggregate = GetMonthAggregate(day);

aggregate.DayAggregate[day.Day] = GetDayAggregatedDayTime(day);
aggregate.AggregateTime = TimeSpan.FromTicks(aggregate.DayAggregate.Sum(x => x.Ticks));
Db.Upsert(aggregate, "TimeAggregates");
}

private TimeAggregate GetMonthAggregate(DateTime day)
{
var aggregate = Db.FirstOrDefault<TimeAggregate>(x => x.Year == day.Year && x.Month == day.Month, "TimeAggregates");
if (aggregate == null)
{
aggregate = new TimeAggregate
{
Year = day.Year,
Month = day.Month
};
var firstDay = new DateTime(day.Year, day.Month, 1);
var lastDay = firstDay.AddMonths(1).AddTicks(-1);
var dailyAggregates = Db.Query<TimeRecord>("TimeRecords")
.Where(x => x.StartTime >= firstDay && x.StartTime <= lastDay)
.ToList()
.GroupBy(x => x.StartTime.Day)
.Select(x => new
{
Day = x.Key,
Time = x.Select(x => (x.EndTime ?? DateTime.Now) - x.StartTime)
.Aggregate(TimeSpan.Zero, (acc, x) => acc + x)
});
foreach (var dailyAggregate in dailyAggregates)
{
aggregate.DayAggregate[dailyAggregate.Day] = dailyAggregate.Time;
}
aggregate.AggregateTime = TimeSpan.FromTicks(aggregate.DayAggregate.Sum(x => x.Ticks));
}

return aggregate;
}

private void EnsureIndexes()
{
var tasks = Db.Database.GetCollection<Task>("Tasks");
tasks.EnsureIndex(x => x.ProjectId);

var timeRecords = Db.Database.GetCollection<TimeRecord>("TimeRecords");
timeRecords.EnsureIndex(x => x.StartTime);

var timeAggregates = Db.Database.GetCollection<TimeAggregate>("TimeAggregates");
timeAggregates.EnsureIndex(x => new { x.Year, x.Month }, true);
}

public int TimeUpdated
Expand Down
Binary file added QBTracker/Images/pngfuel-rec.ico
Binary file not shown.
40 changes: 37 additions & 3 deletions QBTracker/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}" Executed="CloseWindowCommand_OnExecuted"></CommandBinding>
</Window.CommandBindings>
<Grid>
<tb:TaskbarIcon
<tb:TaskbarIcon
Visibility="Visible" VerticalAlignment="Top" HorizontalAlignment="Left"
ToolTip="{Binding VersionString, Mode=OneTime}"
IconSource="/Images/pngfuel.ico"
Expand All @@ -51,8 +51,42 @@
DoubleClickCommand="{x:Static SystemCommands.RestoreWindowCommand}"
DoubleClickCommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
LeftClickCommand="{x:Static SystemCommands.RestoreWindowCommand}"
LeftClickCommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
/>
LeftClickCommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<tb:TaskbarIcon.Style>
<Style TargetType="tb:TaskbarIcon">
<Style.Resources>
<ImageSource x:Key="RecordingIco">pack://application:,,,/Images/pngfuel-rec.ico</ImageSource>
<ImageSource x:Key="RegularIco">pack://application:,,,/Images/pngfuel.ico</ImageSource>
</Style.Resources>
<Style.Triggers>
<DataTrigger Binding="{Binding IsRecording}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<BeginStoryboard.Storyboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="IconSource">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{StaticResource RecordingIco}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<BeginStoryboard.Storyboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="IconSource">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{StaticResource RegularIco}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</tb:TaskbarIcon.Style>
</tb:TaskbarIcon>
<materialDesign:DialogHost VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<materialDesign:Transitioner x:Name="Transitioner" SelectedIndex="{Binding SelectedTransitionIndex}" AutoApplyTransitionOrigins="True" >
<materialDesign:TransitionerSlide OpeningEffect="{materialDesign:TransitionEffect FadeIn}">
Expand Down
14 changes: 14 additions & 0 deletions QBTracker/Model/TimeAggregate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;

namespace QBTracker.Model;

public class TimeAggregate
{
public int Id { get; set; }
public int Year { get; set; }
public int Month { get; set; }
public TimeSpan AggregateTime { get; set; }
public TimeSpan[] DayAggregate { get; set; } = new TimeSpan[31];

}
5 changes: 5 additions & 0 deletions QBTracker/QBTracker.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
</PropertyGroup>

<ItemGroup>
<None Remove="Images\pngfuel-rec.ico" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="EPPlus" Version="4.5.3.3" />
<PackageReference Include="Hardcodet.NotifyIcon.Wpf.NetCore" Version="1.0.18" />
Expand All @@ -28,6 +32,7 @@
</ItemGroup>

<ItemGroup>
<Resource Include="Images\pngfuel-rec.ico" />
<Resource Include="Images\pngfuel.ico" />
</ItemGroup>

Expand Down
20 changes: 19 additions & 1 deletion QBTracker/Resources/QBCalendarStyles.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.ColumnSpan="3" Grid.Row="0" Background="{DynamicResource PrimaryHueMidBrush}" />
<Button x:Name="PART_HeaderButton"
Expand All @@ -436,7 +437,23 @@
</TextBlock>
<Button x:Name="PART_NextButton" Grid.Row="1" Grid.Column="2" Focusable="False" HorizontalAlignment="Right" Height="36" Template="{StaticResource NextButtonTemplate}" Width="32"
Margin="0 0 6 0" Foreground="{TemplateBinding Foreground}" />

<TextBlock x:Name="CurrentMonthTimeAggregateTextBlock"
HorizontalAlignment="Center" VerticalAlignment="Center"
Margin="4, 0, 4, 4"
FontSize="14"
Grid.Row="3" Grid.Column="1"
FontWeight="Light"
Foreground="{DynamicResource SecondaryHueDarkBrush}">
<TextBlock.RenderTransform>
<TranslateTransform X="0" Y="0"/>
</TextBlock.RenderTransform>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MonthTimeConverter}">
<Binding Path="DisplayDate" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Calendar}}"></Binding>
<Binding Path="TimeUpdated" Source="{StaticResource Repository}"></Binding>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Grid x:Name="MonthViewWrapperGrid"
Grid.ColumnSpan="3"
Grid.Row="2"
Expand Down Expand Up @@ -474,6 +491,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
</Grid>
</Grid>
Expand Down
1 change: 1 addition & 0 deletions QBTracker/Resources/Resources.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
<converters:EnumHumanizerConverter x:Key="EnumHumanizerConverter" />
<converters:NotNullToBoolConverter x:Key="NotNullToBoolConverter" />
<converters:DayFillConverter x:Key="DayFillConverter" />
<converters:MonthTimeConverter x:Key="MonthTimeConverter" />
</ResourceDictionary>
Loading

0 comments on commit 02f572d

Please sign in to comment.