Compare commits

...

5 Commits

9 changed files with 69 additions and 37 deletions

View File

@ -1,4 +1,9 @@
## Unreleased
* Allow deleting sessions
* Redirect after adding game/platform/purchase/session
* Fix display of duration_manual
* Fix display of duration_calculated, display durations less than a minute
* Make the "Finish now?" button on session list work
* Hide navigation bar items if there are no games/purchases/sessions
* Set default version to "git-main" to indicate development environment
* Add homepage, link to it from the logo

View File

@ -12,5 +12,5 @@ COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
USER timetracker
EXPOSE 8000
ENV VERSION_NUMBER 0.1.0-4-g166dd71
ENV VERSION_NUMBER 0.1.0-18-gb8a15e4
ENTRYPOINT [ "/entrypoint.sh" ]

View File

View File

@ -0,0 +1,7 @@
from datetime import datetime
from django.conf import settings
from zoneinfo import ZoneInfo
def now():
return datetime.now(ZoneInfo(settings.TIME_ZONE))

View File

@ -1,5 +1,7 @@
from django.db import models
from datetime import timedelta
from datetime import datetime
from django.conf import settings
from zoneinfo import ZoneInfo
class Game(models.Model):
@ -40,14 +42,17 @@ class Session(models.Model):
mark = ", manual" if self.duration_manual != None else ""
return f"{str(self.purchase)} {str(self.timestamp_start.date())} ({self.duration_any()}{mark})"
def finish_now(self):
self.timestamp_end = datetime.now(ZoneInfo(settings.TIME_ZONE))
def duration_seconds(self):
if self.timestamp_end == None or self.timestamp_start == None:
if self.duration_manual == None:
if self.timestamp_end == None or self.timestamp_start == None:
return 0
else:
value = self.duration_manual
else:
value = self.timestamp_end - self.timestamp_start
else:
value = self.duration_manual
return value.total_seconds()
def duration_formatted(self):
@ -55,7 +60,10 @@ class Session(models.Model):
if seconds == 0:
return seconds
hours, remainder = divmod(seconds, 3600)
minutes = remainder % 60
minutes = remainder // 60
if hours == 0 and minutes == 0:
return "less than a minute"
else:
hour_string = f"{int(hours)}h" if hours != 0 else ""
minute_string = f"{int(minutes)}m" if minutes != 0 else ""
return f"{hour_string}{minute_string}"

View File

@ -766,8 +766,8 @@ select {
max-width: 1024px;
}
.grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
.grid-cols-5 {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.flex-col {
@ -819,11 +819,6 @@ select {
border-color: rgb(229 231 235 / var(--tw-border-opacity));
}
.border-red-800 {
--tw-border-opacity: 1;
border-color: rgb(153 27 27 / var(--tw-border-opacity));
}
.border-red-900 {
--tw-border-opacity: 1;
border-color: rgb(127 29 29 / var(--tw-border-opacity));
@ -943,21 +938,6 @@ form input[type=submit] {
border-color: rgb(255 255 255 / var(--tw-border-opacity));
}
.hover\:bg-red-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
}
.hover\:bg-red-500:hover {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.hover\:bg-yellow-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(161 98 7 / var(--tw-bg-opacity));
}
.hover\:bg-orange-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(194 65 12 / var(--tw-bg-opacity));

View File

@ -9,17 +9,18 @@
<a class="dark:text-white hover:underline" href="{% url 'list_sessions' %}">View all sessions</a>
</div>
{% endif %}
<div class="grid grid-cols-4 gap-4 shadow rounded-xl max-w-screen-lg mx-auto dark:bg-slate-700 p-2 justify-center">
<div class="grid grid-cols-5 gap-4 shadow rounded-xl max-w-screen-lg mx-auto dark:bg-slate-700 p-2 justify-center">
<div class="dark:border-white dark:text-slate-300 text-lg">Name</div>
<div class="dark:border-white dark:text-slate-300 text-lg">Start</div>
<div class="dark:border-white dark:text-slate-300 text-lg">End</div>
<div class="dark:border-white dark:text-slate-300 text-lg">Duration</div>
<div></div>
{% for data in dataset %}
<div class=""><a class="dark:text-white hover:underline" href="{% url 'list_sessions' data.purchase.id %}">{{ data.purchase }}</a></div>
<div class="dark:text-slate-400">{{ data.timestamp_start | date:"d/m/Y H:i" }}</div>
<div class="dark:text-slate-400">
{% if data.unfinished %}
Not finished yet. <button class="bg-red-700 hover:bg-orange-700 border border-red-900 hover:border-dotted hover:border-white rounded p-1 text-white text-sm">Finish now?</button>
Not finished yet. <a href="{% url 'update_session' data.id %}"><button class="bg-red-700 hover:bg-orange-700 border border-red-900 hover:border-dotted hover:border-white rounded p-1 text-white text-sm">Finish now?</button></a>
{% elif data.duration_manual %}
MANUAL
{% else %}
@ -27,6 +28,9 @@
{% endif %}
</div>
<div class="dark:text-slate-400">{{ data.duration_formatted }}{% if data.duration_manual %} (M){% endif %}</div>
<div>
<a href="{% url 'delete_session' data.id %}"><button></button></a>
</div>
{% endfor %}
</div>
{% endblock content %}

View File

@ -7,6 +7,16 @@ urlpatterns = [
path("add-game/", views.add_game, name="add_game"),
path("add-platform/", views.add_platform, name="add_platform"),
path("add-session/", views.add_session, name="add_session"),
path(
"update-session/by-session/<int:session_id>",
views.update_session,
name="update_session",
),
path(
"delete_session/by-id/<int:session_id>",
views.delete_session,
name="delete_session",
),
path("add-purchase/", views.add_purchase, name="add_purchase"),
path("list-sessions/", views.list_sessions, name="list_sessions"),
path(

View File

@ -1,10 +1,11 @@
from django.shortcuts import render
from django.shortcuts import render, redirect
from .models import Game, Platform, Purchase, Session
from .forms import SessionForm, PurchaseForm, GameForm, PlatformForm
from datetime import datetime
from zoneinfo import ZoneInfo
from django.conf import settings
from common.util.time import now as now_with_tz
def model_counts(request):
@ -18,16 +19,30 @@ def model_counts(request):
def add_session(request):
context = {}
now = datetime.now()
initial = {"timestamp_start": now, "timestamp_end": now}
now = now_with_tz()
initial = {"timestamp_start": now}
form = SessionForm(request.POST or None, initial=initial)
if form.is_valid():
form.save()
return redirect("list_sessions")
context["form"] = form
return render(request, "add_session.html", context)
def update_session(request, session_id=None):
session = Session.objects.get(id=session_id)
session.finish_now()
session.save()
return redirect("list_sessions")
def delete_session(request, session_id=None):
session = Session.objects.get(id=session_id)
session.delete()
return redirect("list_sessions")
def list_sessions(request, purchase_id=None):
context = {}
@ -54,6 +69,7 @@ def add_purchase(request):
form = PurchaseForm(request.POST or None, initial=initial)
if form.is_valid():
form.save()
return redirect("index")
context["form"] = form
context["title"] = "Add New Purchase"
@ -65,6 +81,7 @@ def add_game(request):
form = GameForm(request.POST or None)
if form.is_valid():
form.save()
return redirect("index")
context["form"] = form
context["title"] = "Add New Game"
@ -76,6 +93,7 @@ def add_platform(request):
form = PlatformForm(request.POST or None)
if form.is_valid():
form.save()
return redirect("index")
context["form"] = form
context["title"] = "Add New Platform"