From 57d4fd72120aae5018089fa1eb7856b27ec4ce74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kucharczyk?= Date: Wed, 1 Nov 2023 20:18:39 +0100 Subject: [PATCH] Add yearly stats page Fixes #15 --- CHANGELOG.md | 3 ++ games/static/base.css | 28 +++++++++-------- games/templates/stats.html | 64 ++++++++++++++++++++++++++++++++++++++ games/urls.py | 5 +++ games/views.py | 43 +++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 games/templates/stats.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d25d71..7eabf55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Unreleased +### New +* Add yearly stats page (https://git.kucharczyk.xyz/lukas/timetracker/issues/15) + ### Enhancements * Add a button to start session from game overview diff --git a/games/static/base.css b/games/static/base.css index 0c5f72d..c755028 100644 --- a/games/static/base.css +++ b/games/static/base.css @@ -791,6 +791,16 @@ select { margin-bottom: 1rem; } +.my-5 { + margin-top: 1.25rem; + margin-bottom: 1.25rem; +} + +.my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + .mb-1 { margin-bottom: 0.25rem; } @@ -889,10 +899,6 @@ select { animation: spin 1s linear infinite; } -.flex-row { - flex-direction: row; -} - .flex-col { flex-direction: column; } @@ -905,10 +911,6 @@ select { align-items: center; } -.items-baseline { - align-items: baseline; -} - .justify-center { justify-content: center; } @@ -1017,6 +1019,11 @@ select { line-height: 2.5rem; } +.text-5xl { + font-size: 3rem; + line-height: 1; +} + .text-base { font-size: 1rem; line-height: 1.5rem; @@ -1037,11 +1044,6 @@ select { line-height: 1rem; } -.text-sm { - font-size: 0.875rem; - line-height: 1.25rem; -} - .font-semibold { font-weight: 600; } diff --git a/games/templates/stats.html b/games/templates/stats.html new file mode 100644 index 0000000..9ff2c12 --- /dev/null +++ b/games/templates/stats.html @@ -0,0 +1,64 @@ +{% extends "base.html" %} + +{% block title %}{{ title }}{% endblock title %} + +{% load static %} + +{% block content %} +
+

Stats for {{ year }}

+ + + + + + + + + + + + + + +
Total hoursTotal gamesTotal 2023 games
{{ total_hours }}{{ total_games }}{{ total_2023_games }}
+

Top games by playtime

+ + + + + + + + + {% for purchase in top_10_by_playtime %} + + + + + {% endfor %} + +
NamePlaytime (hours)
+ {{ purchase.edition.name }} + + + {{ purchase.formatted_playtime }}
+

Platforms by playtime

+ + + + + + + + + {% for item in total_playtime_per_platform %} + + + + + {% endfor %} + +
PlatformPlaytime (hours)
{{ item.platform_name }} {{ item.formatted_playtime }}
+
+{% endblock content %} diff --git a/games/urls.py b/games/urls.py index ec081c2..0bec6ce 100644 --- a/games/urls.py +++ b/games/urls.py @@ -73,4 +73,9 @@ urlpatterns = [ {"filter": "ownership_type"}, name="list_sessions_by_ownership_type", ), + path( + "stats/", + views.stats, + name="stats_by_year", + ), ] diff --git a/games/views.py b/games/views.py index bf8f781..b6d123b 100644 --- a/games/views.py +++ b/games/views.py @@ -5,6 +5,7 @@ from common.time import now as now_with_tz from common.time import format_duration from django.conf import settings from django.shortcuts import redirect, render +from django.db.models import Sum, F from .forms import ( GameForm, @@ -229,6 +230,48 @@ def list_sessions( return render(request, "list_sessions.html", context) +def stats(request, year: int): + first_day_of_year = datetime(year, 1, 1) + year_sessions = Session.objects.filter(timestamp_start__gte=first_day_of_year) + year_purchases = Purchase.objects.filter(session__in=year_sessions).distinct() + year_purchases_with_playtime = year_purchases.annotate( + total_playtime=Sum( + F("session__duration_calculated") + F("session__duration_manual") + ) + ) + top_10_by_playtime = year_purchases_with_playtime.order_by("-total_playtime")[:10] + for purchase in top_10_by_playtime: + purchase.formatted_playtime = format_duration(purchase.total_playtime, "%2.0H") + + total_playtime_per_platform = ( + year_sessions.values("purchase__platform__name") # Group by platform name + .annotate( + total_playtime=Sum(F("duration_calculated") + F("duration_manual")) + ) # Sum the duration_calculated for each group + .annotate(platform_name=F("purchase__platform__name")) # Rename the field + .values( + "platform_name", "total_playtime" + ) # Select the renamed field and total_playtime + .order_by("-total_playtime") # Optional: Order by the renamed platform name + ) + for item in total_playtime_per_platform: + item["formatted_playtime"] = format_duration(item["total_playtime"], "%2.0H") + + context = { + "total_hours": format_duration( + year_sessions.total_duration_unformatted(), "%2.0H" + ), + "total_games": year_purchases.count(), + "total_2023_games": year_purchases.filter(edition__year_released=year).count(), + "top_10_by_playtime_formatted": top_10_by_playtime, + "top_10_by_playtime": top_10_by_playtime, + "year": year, + "total_playtime_per_platform": total_playtime_per_platform, + } + + return render(request, "stats.html", context) + + def add_purchase(request): context = {} now = datetime.now()