Compare commits
5 Commits
391fcc79a8
...
f13ed8a078
Author | SHA1 | Date |
---|---|---|
Lukáš Kucharczyk | f13ed8a078 | |
Lukáš Kucharczyk | 02d5adcb3c | |
Lukáš Kucharczyk | d6fb16bb74 | |
Lukáš Kucharczyk | 71b90b8202 | |
Lukáš Kucharczyk | 3ee36932c3 |
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -1,3 +1,15 @@
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### New
|
||||||
|
* Add Stats to the main navigation
|
||||||
|
* Allow selecting year on the Stats page
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
* Make navigation more compact
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Correctly limit sessions to a single year for stats
|
||||||
|
|
||||||
## 1.2.0 / 2023-11-01 20:18+01:00
|
## 1.2.0 / 2023-11-01 20:18+01:00
|
||||||
|
|
||||||
### New
|
### New
|
||||||
|
|
|
@ -755,6 +755,10 @@ select {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.bottom-2 {
|
.bottom-2 {
|
||||||
bottom: 0.5rem;
|
bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
@ -791,11 +795,6 @@ select {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-5 {
|
|
||||||
margin-top: 1.25rem;
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-6 {
|
.my-6 {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
|
@ -805,6 +804,10 @@ select {
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mb-10 {
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mb-4 {
|
.mb-4 {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -829,6 +832,10 @@ select {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inline-block {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.inline {
|
.inline {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
@ -873,6 +880,10 @@ select {
|
||||||
width: 1.75rem;
|
width: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-auto {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.w-full {
|
.w-full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -959,6 +970,11 @@ select {
|
||||||
border-color: rgb(100 116 139 / var(--tw-border-opacity));
|
border-color: rgb(100 116 139 / var(--tw-border-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-gray-200 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.bg-green-600 {
|
.bg-green-600 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(22 163 74 / var(--tw-bg-opacity));
|
background-color: rgb(22 163 74 / var(--tw-bg-opacity));
|
||||||
|
@ -983,6 +999,11 @@ select {
|
||||||
padding-right: 0.5rem;
|
padding-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.px-4 {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.py-1 {
|
.py-1 {
|
||||||
padding-top: 0.25rem;
|
padding-top: 0.25rem;
|
||||||
padding-bottom: 0.25rem;
|
padding-bottom: 0.25rem;
|
||||||
|
@ -1001,6 +1022,10 @@ select {
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pt-1 {
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -1052,6 +1077,11 @@ select {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-gray-700 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(55 65 81 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.text-slate-300 {
|
.text-slate-300 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(203 213 225 / var(--tw-text-opacity));
|
color: rgb(203 213 225 / var(--tw-text-opacity));
|
||||||
|
@ -1287,6 +1317,11 @@ th label {
|
||||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:bg-gray-400:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:bg-green-700:hover {
|
.hover\:bg-green-700:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
|
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
|
||||||
|
@ -1334,6 +1369,10 @@ th label {
|
||||||
--tw-ring-offset-color: #ddd6fe;
|
--tw-ring-offset-color: #ddd6fe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.group:hover .group-hover\:block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
:is(.dark .dark\:bg-gray-800) {
|
:is(.dark .dark\:bg-gray-800) {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
|
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
|
||||||
|
|
|
@ -25,19 +25,37 @@
|
||||||
<div class="w-full md:block md:w-auto">
|
<div class="w-full md:block md:w-auto">
|
||||||
<ul
|
<ul
|
||||||
class="flex flex-col md:flex-row p-4 mt-4 dark:text-white">
|
class="flex flex-col md:flex-row p-4 mt-4 dark:text-white">
|
||||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_game' %}">New Game</a></li>
|
<li class="relative group">
|
||||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_platform' %}">New Platform</a></li>
|
<a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_game' %}">New</a>
|
||||||
{% if game_available and platform_available %}
|
<ul class="absolute hidden text-gray-700 pt-1 group-hover:block w-auto whitespace-nowrap">
|
||||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_edition' %}">New Edition</a></li>
|
{% if purchase_available %}
|
||||||
{% endif %}
|
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_device' %}">Device</a></li>
|
||||||
{% if edition_available %}
|
{% endif %}
|
||||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_purchase' %}">New Purchase</a></li>
|
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_game' %}">Game</a></li>
|
||||||
{% endif %}
|
{% if game_available and platform_available %}
|
||||||
{% if purchase_available %}
|
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_edition' %}">Edition</a></li>
|
||||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_session' %}">New Session</a></li>
|
{% endif %}
|
||||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_device' %}">New Device</a></li>
|
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_platform' %}">Platform</a></li>
|
||||||
{% endif %}
|
{% if edition_available %}
|
||||||
|
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_purchase' %}">Purchase</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if purchase_available %}
|
||||||
|
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_session' %}">Session</a></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
{% if session_count > 0 %}
|
{% if session_count > 0 %}
|
||||||
|
<li class="relative group">
|
||||||
|
<a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'stats_current_year' %}">Stats</a>
|
||||||
|
<ul class="absolute hidden text-gray-700 pt-1 group-hover:block">
|
||||||
|
{% for year in stats_dropdown_year_range %}
|
||||||
|
<li>
|
||||||
|
<a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'stats_by_year' year %}">{{ year }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'list_sessions' %}">All Sessions</a></li>
|
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'list_sessions' %}">All Sessions</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -6,13 +6,21 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="dark:text-white max-w-sm sm:max-w-xl lg:max-w-3xl mx-auto">
|
<div class="dark:text-white max-w-sm sm:max-w-xl lg:max-w-3xl mx-auto">
|
||||||
<h1 class="text-5xl text-center my-6">Stats for {{ year }}</h1>
|
<div class="flex justify-center items-center">
|
||||||
|
<form method="get" class="text-center">
|
||||||
|
<label class="text-5xl text-center inline-block mb-10" for="yearSelect">Stats for:</label>
|
||||||
|
<select name="year" id="yearSelect" onchange="this.form.submit();" class="mx-2">
|
||||||
|
<option value="2022" {% if year == 2022 %}selected{% endif %}>2022</option>
|
||||||
|
<option value="2023" {% if year == 2023 %}selected{% endif %}>2023</option>
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<table class="responsive-table">
|
<table class="responsive-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Total hours</th>
|
<th class="px-2 sm:px-4 md:px-6 md:py-2">Total hours</th>
|
||||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Total games</th>
|
<th class="px-2 sm:px-4 md:px-6 md:py-2">Total games</th>
|
||||||
<th class="px-2 sm:px-4 md:px-6 md:py-2">Total 2023 games</th>
|
<th class="px-2 sm:px-4 md:px-6 md:py-2">Released that year</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -73,6 +73,7 @@ urlpatterns = [
|
||||||
{"filter": "ownership_type"},
|
{"filter": "ownership_type"},
|
||||||
name="list_sessions_by_ownership_type",
|
name="list_sessions_by_ownership_type",
|
||||||
),
|
),
|
||||||
|
path("stats/", views.stats, name="stats_current_year"),
|
||||||
path(
|
path(
|
||||||
"stats/<int:year>",
|
"stats/<int:year>",
|
||||||
views.stats,
|
views.stats,
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from zoneinfo import ZoneInfo
|
|
||||||
|
|
||||||
from common.time import now as now_with_tz
|
|
||||||
from common.time import format_duration
|
from common.time import format_duration
|
||||||
|
from common.time import now as now_with_tz
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.shortcuts import redirect, render
|
|
||||||
from django.db.models import Sum, F
|
from django.db.models import Sum, F
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
from django.urls import reverse
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from .forms import (
|
from .forms import (
|
||||||
GameForm,
|
GameForm,
|
||||||
|
@ -28,6 +29,10 @@ def model_counts(request):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def stats_dropdown_year_range(request):
|
||||||
|
return {"stats_dropdown_year_range": range(2022, 2024)}
|
||||||
|
|
||||||
|
|
||||||
def add_session(request):
|
def add_session(request):
|
||||||
context = {}
|
context = {}
|
||||||
initial = {}
|
initial = {}
|
||||||
|
@ -230,9 +235,17 @@ def list_sessions(
|
||||||
return render(request, "list_sessions.html", context)
|
return render(request, "list_sessions.html", context)
|
||||||
|
|
||||||
|
|
||||||
def stats(request, year: int):
|
def stats(request, year: int = 0):
|
||||||
|
selected_year = request.GET.get("year")
|
||||||
|
if selected_year:
|
||||||
|
return HttpResponseRedirect(reverse("stats_by_year", args=[selected_year]))
|
||||||
|
if year == 0:
|
||||||
|
year = now_with_tz().year
|
||||||
first_day_of_year = datetime(year, 1, 1)
|
first_day_of_year = datetime(year, 1, 1)
|
||||||
year_sessions = Session.objects.filter(timestamp_start__gte=first_day_of_year)
|
last_day_of_year = datetime(year + 1, 1, 1)
|
||||||
|
year_sessions = Session.objects.filter(
|
||||||
|
timestamp_start__gte=first_day_of_year
|
||||||
|
).filter(timestamp_start__lt=last_day_of_year)
|
||||||
year_purchases = Purchase.objects.filter(session__in=year_sessions).distinct()
|
year_purchases = Purchase.objects.filter(session__in=year_sessions).distinct()
|
||||||
year_purchases_with_playtime = year_purchases.annotate(
|
year_purchases_with_playtime = year_purchases.annotate(
|
||||||
total_playtime=Sum(
|
total_playtime=Sum(
|
||||||
|
|
|
@ -68,6 +68,7 @@ TEMPLATES = [
|
||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
"games.views.model_counts",
|
"games.views.model_counts",
|
||||||
|
"games.views.stats_dropdown_year_range",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue