Skip to content

Commit 91bceaa

Browse files
authored
Merge pull request #49 from veloek/more-stats
Changes to statistics page
2 parents 1bf0b63 + 42a9cc3 commit 91bceaa

File tree

5 files changed

+180
-56
lines changed

5 files changed

+180
-56
lines changed

Tevling/Pages/Statistics.razor

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,54 @@
33

44
<h1 class="title mb-5">Statistics</h1>
55

6-
<div class="d-flex flex-wrap p-2">
7-
<canvas id="totalDistanceChart"></canvas>
8-
</div>
6+
<label>Months:</label>
7+
<InputSelect @bind-Value="@NumberOfMonthsToReview" @oninput="DrawChart" class="mb-2">
8+
@for (int i = 3; i <= 36; i+=3)
9+
{
10+
<option value="@i">@i</option>
11+
}
12+
</InputSelect>
13+
<ButtonGroup
14+
Items="Enum.GetValues<ChallengeMeasurement>()"
15+
T="ChallengeMeasurement"
16+
@bind-SelectedItem="Measurement"
17+
OnButtonSelected="@DrawChart">
18+
</ButtonGroup>
919

1020
<div class="d-flex flex-wrap p-2">
11-
<canvas id="totalElevationChart"></canvas>
21+
<canvas id="TheChart"></canvas>
1222
</div>
1323

14-
<div class="d-flex flex-wrap p-2">
15-
<canvas id="totalTimeChart"></canvas>
16-
</div>
24+
<h2 class="title mb-5">Summary</h2>
25+
<p>The last <b>@NumberOfMonthsToReview</b> month(s) you've:</p>
26+
27+
@switch (Measurement)
28+
{
29+
case ChallengeMeasurement.Distance:
30+
@foreach (KeyValuePair<string, float[]> kvp in Distances)
31+
{
32+
<p>- Traversed <b>@Math.Round(kvp.Value.Sum(), 1)</b>km by @kvp.Key</p>
33+
}
34+
35+
<p>A total of <b>@Math.Round(Distances.Sum(kvp => kvp.Value.Sum()), 1)</b>km</p>
36+
break;
37+
case ChallengeMeasurement.Elevation:
38+
@foreach (KeyValuePair<string, float[]> kvp in Elevations)
39+
{
40+
<p>- Ascended <b>@Math.Round(kvp.Value.Sum(), 1)</b>m by @kvp.Key</p>
41+
}
42+
43+
<p>A total of <b>@Math.Round(Elevations.Sum(kvp => kvp.Value.Sum()), 1)</b>m</p>
44+
break;
45+
case ChallengeMeasurement.Time:
46+
@foreach (KeyValuePair<string, float[]> kvp in Durations)
47+
{
48+
<p>- Spent <b>@Math.Round(kvp.Value.Sum(), 1)</b> hours on @kvp.Key</p>
49+
}
50+
51+
<p>A total of <b>@Math.Round(Durations.Sum(kvp => kvp.Value.Sum()), 1)</b> hours</p>
52+
break;
53+
default:
54+
throw new ArgumentOutOfRangeException();
55+
}
56+

Tevling/Pages/Statistics.razor.cs

Lines changed: 81 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@ public partial class Statistics : ComponentBase, IAsyncDisposable
1313
private Activity[] _activities = [];
1414
private IJSObjectReference? _module;
1515

16+
private int NumberOfMonthsToReview { get; set; } = 3;
17+
private ChallengeMeasurement Measurement { get; set; } = ChallengeMeasurement.Distance;
18+
private Dictionary<string, float[]> Distances { get; set; } = [];
19+
private Dictionary<string, float[]> Elevations { get; set; } = [];
20+
private Dictionary<string, float[]> Durations { get; set; } = [];
21+
1622
protected override async Task OnInitializedAsync()
1723
{
1824
_athlete = await AuthenticationService.GetCurrentAthleteAsync();
19-
20-
ActivityFilter filter = new(_athlete.Id, false, DateTimeOffset.Now.AddMonths(-2).ToFirstOfTheMonth());
21-
_activities = await ActivityService.GetActivitiesAsync(filter);
25+
UpdateMeasurementData();
2226
}
2327

2428
private Dictionary<string, float[]> GetAggregatedMeasurementData(Func<Activity, float> selector, int monthCount = 3)
@@ -33,70 +37,98 @@ private Dictionary<string, float[]> GetAggregatedMeasurementData(Func<Activity,
3337
.Select(m =>
3438
{
3539
int month = now.AddMonths(m).Month;
40+
int year = now.AddMonths(m).Year;
3641
return g
37-
.Where(a => a.Details.StartDate.Month == month)
42+
.Where(a => a.Details.StartDate.Month == month && a.Details.StartDate.Year == year)
3843
.Sum(selector);
3944
})
4045
.ToArray()
4146
)
4247
.Where(d => d.Value.Any(v => v > 0))
4348
.ToDictionary();
4449

45-
// if (aggregatedData.Count != 0)
46-
// {
47-
// aggregatedData["Total"] =
48-
// [
49-
// .. aggregatedData.Values.Aggregate((sum, next) => [.. sum.Zip(next, (a, b) => a + b)]),
50-
// ];
51-
// }
52-
5350
return aggregatedData;
5451
}
5552

53+
private static string[] CreateMonthArray(int monthCount)
54+
{
55+
return
56+
[
57+
.. Enumerable.Range(0, monthCount)
58+
.Select(i =>
59+
{
60+
DateTime month = DateTime.Now.AddMonths(-i);
61+
return month.ToString(month.Month == 1 ? "MMMM-yy" : "MMMM");
62+
})
63+
.Reverse()
64+
];
65+
}
66+
5667
protected override async Task OnAfterRenderAsync(bool firstRender)
5768
{
5869
if (!firstRender)
5970
return;
6071

72+
await DrawChart();
73+
}
74+
75+
private async Task UpdateMeasurementData()
76+
{
77+
ActivityFilter filter = new(
78+
_athlete.Id,
79+
false,
80+
DateTimeOffset.Now.AddMonths(-NumberOfMonthsToReview + 1).ToFirstOfTheMonth());
81+
_activities = await ActivityService.GetActivitiesAsync(filter);
82+
83+
Distances = GetAggregatedMeasurementData(
84+
a => a.Details.DistanceInMeters / 1000,
85+
NumberOfMonthsToReview);
86+
Elevations = GetAggregatedMeasurementData(a => a.Details.TotalElevationGain, NumberOfMonthsToReview);
87+
Durations = GetAggregatedMeasurementData(
88+
a => (float)a.Details.MovingTimeInSeconds / 3600,
89+
NumberOfMonthsToReview);
90+
}
91+
92+
private async Task DrawChart()
93+
{
6194
_module = await Js.InvokeAsync<IJSObjectReference>("import", "./Pages/Statistics.razor.js");
6295

63-
string[] lastThreeMonths = new int[]
64-
{
65-
DateTimeOffset.Now.AddMonths(-2).Month,
66-
DateTimeOffset.Now.AddMonths(-1).Month,
67-
DateTimeOffset.Now.Month,
68-
}
69-
.Select(CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName)
70-
.ToArray();
71-
72-
Dictionary<string, float[]> distanceLastThreeMonths =
73-
GetAggregatedMeasurementData(a => (float)a.Details.DistanceInMeters / 1000);
74-
Dictionary<string, float[]> elevationLastThreeMonths =
75-
GetAggregatedMeasurementData(a => a.Details.TotalElevationGain);
76-
Dictionary<string, float[]> timeLastThreeMonths =
77-
GetAggregatedMeasurementData(a => (float)a.Details.MovingTimeInSeconds / 3600);
78-
79-
await _module.InvokeVoidAsync(
80-
"drawChart",
81-
distanceLastThreeMonths,
82-
lastThreeMonths,
83-
"totalDistanceChart",
84-
"Total Distance [km]",
85-
"km");
86-
await _module.InvokeVoidAsync(
87-
"drawChart",
88-
elevationLastThreeMonths,
89-
lastThreeMonths,
90-
"totalElevationChart",
91-
"Total Elevation [m]",
92-
"m");
93-
await _module.InvokeVoidAsync(
94-
"drawChart",
95-
timeLastThreeMonths,
96-
lastThreeMonths,
97-
"totalTimeChart",
98-
"Total Time [h]",
99-
"h");
96+
string[] months = CreateMonthArray(NumberOfMonthsToReview);
97+
98+
UpdateMeasurementData();
99+
100+
switch (Measurement)
101+
{
102+
case ChallengeMeasurement.Distance:
103+
await _module.InvokeVoidAsync(
104+
"drawChart",
105+
Distances,
106+
months,
107+
"TheChart",
108+
"Total Distance [km]",
109+
"km");
110+
break;
111+
case ChallengeMeasurement.Elevation:
112+
await _module.InvokeVoidAsync(
113+
"drawChart",
114+
Elevations,
115+
months,
116+
"TheChart",
117+
"Total Elevation [m]",
118+
"m");
119+
break;
120+
case ChallengeMeasurement.Time:
121+
await _module.InvokeVoidAsync(
122+
"drawChart",
123+
Durations,
124+
months,
125+
"TheChart",
126+
"Total Time [h]",
127+
"h");
128+
break;
129+
default:
130+
throw new Exception("Unknown challenge measurement");
131+
}
100132
}
101133

102134
public async ValueTask DisposeAsync()

Tevling/Shared/ButtonGroup.razor

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@typeparam T
2+
3+
<div class="ButtonGroup">
4+
@foreach (T item in Items)
5+
{
6+
<button type="button"
7+
class="@GetButtonCssClass(item)"
8+
@onclick="() => HandleButtonClick(item)">
9+
@item
10+
</button>
11+
}
12+
</div>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using Microsoft.AspNetCore.Components;
2+
3+
namespace Tevling.Shared;
4+
5+
public partial class ButtonGroup<T> : ComponentBase
6+
{
7+
[Parameter] public IEnumerable<T> Items { get; set; } = [];
8+
[Parameter] public T? SelectedItem { get; set; }
9+
10+
[Parameter] public EventCallback<T> SelectedItemChanged { get; set; }
11+
[Parameter] public EventCallback<T> OnButtonSelected { get; set; }
12+
13+
protected override void OnInitialized()
14+
{
15+
if (SelectedItem is null && Items.Any())
16+
{
17+
SelectedItem = Items.First();
18+
}
19+
}
20+
21+
private string GetButtonCssClass(T item)
22+
{
23+
return SelectedItem?.Equals(item) == true ? "btn btn-primary active me-2" : "btn btn-secondary me-2";
24+
}
25+
26+
private async Task HandleButtonClick(T item)
27+
{
28+
SelectedItem = item;
29+
if(SelectedItemChanged.HasDelegate)
30+
{
31+
await SelectedItemChanged.InvokeAsync(item);
32+
await OnButtonSelected.InvokeAsync(item);
33+
}
34+
}
35+
}
36+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.ButtonGroup {
2+
display: flex;
3+
gap: 20px;
4+
}

0 commit comments

Comments
 (0)