Compare commits

...

5 Commits

9 changed files with 69 additions and 37 deletions

View File

@ -1,4 +1,9 @@
## Unreleased ## 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 * Hide navigation bar items if there are no games/purchases/sessions
* Set default version to "git-main" to indicate development environment * Set default version to "git-main" to indicate development environment
* Add homepage, link to it from the logo * Add homepage, link to it from the logo

View File

@ -12,5 +12,5 @@ COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh RUN chmod +x /entrypoint.sh
USER timetracker USER timetracker
EXPOSE 8000 EXPOSE 8000
ENV VERSION_NUMBER 0.1.0-4-g166dd71 ENV VERSION_NUMBER 0.1.0-18-gb8a15e4
ENTRYPOINT [ "/entrypoint.sh" ] 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 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): class Game(models.Model):
@ -40,14 +42,17 @@ class Session(models.Model):
mark = ", manual" if self.duration_manual != None else "" mark = ", manual" if self.duration_manual != None else ""
return f"{str(self.purchase)} {str(self.timestamp_start.date())} ({self.duration_any()}{mark})" 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): def duration_seconds(self):
if self.timestamp_end == None or self.timestamp_start == None: if self.duration_manual == None:
if self.duration_manual == None: if self.timestamp_end == None or self.timestamp_start == None:
return 0 return 0
else: else:
value = self.duration_manual value = self.timestamp_end - self.timestamp_start
else: else:
value = self.timestamp_end - self.timestamp_start value = self.duration_manual
return value.total_seconds() return value.total_seconds()
def duration_formatted(self): def duration_formatted(self):
@ -55,10 +60,13 @@ class Session(models.Model):
if seconds == 0: if seconds == 0:
return seconds return seconds
hours, remainder = divmod(seconds, 3600) hours, remainder = divmod(seconds, 3600)
minutes = remainder % 60 minutes = remainder // 60
hour_string = f"{int(hours)}h" if hours != 0 else "" if hours == 0 and minutes == 0:
minute_string = f"{int(minutes)}m" if minutes != 0 else "" return "less than a minute"
return f"{hour_string}{minute_string}" 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}"
def duration_any(self): def duration_any(self):
return ( return (

View File

@ -766,8 +766,8 @@ select {
max-width: 1024px; max-width: 1024px;
} }
.grid-cols-4 { .grid-cols-5 {
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(5, minmax(0, 1fr));
} }
.flex-col { .flex-col {
@ -819,11 +819,6 @@ select {
border-color: rgb(229 231 235 / var(--tw-border-opacity)); 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 { .border-red-900 {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(127 29 29 / var(--tw-border-opacity)); 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)); 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 { .hover\:bg-orange-700:hover {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(194 65 12 / var(--tw-bg-opacity)); 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> <a class="dark:text-white hover:underline" href="{% url 'list_sessions' %}">View all sessions</a>
</div> </div>
{% endif %} {% 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">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">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">End</div>
<div class="dark:border-white dark:text-slate-300 text-lg">Duration</div> <div class="dark:border-white dark:text-slate-300 text-lg">Duration</div>
<div></div>
{% for data in dataset %} {% 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=""><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">{{ data.timestamp_start | date:"d/m/Y H:i" }}</div>
<div class="dark:text-slate-400"> <div class="dark:text-slate-400">
{% if data.unfinished %} {% 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 %} {% elif data.duration_manual %}
MANUAL MANUAL
{% else %} {% else %}
@ -27,6 +28,9 @@
{% endif %} {% endif %}
</div> </div>
<div class="dark:text-slate-400">{{ data.duration_formatted }}{% if data.duration_manual %} (M){% 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 %} {% endfor %}
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -7,6 +7,16 @@ urlpatterns = [
path("add-game/", views.add_game, name="add_game"), path("add-game/", views.add_game, name="add_game"),
path("add-platform/", views.add_platform, name="add_platform"), path("add-platform/", views.add_platform, name="add_platform"),
path("add-session/", views.add_session, name="add_session"), 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("add-purchase/", views.add_purchase, name="add_purchase"),
path("list-sessions/", views.list_sessions, name="list_sessions"), path("list-sessions/", views.list_sessions, name="list_sessions"),
path( 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 .models import Game, Platform, Purchase, Session
from .forms import SessionForm, PurchaseForm, GameForm, PlatformForm from .forms import SessionForm, PurchaseForm, GameForm, PlatformForm
from datetime import datetime from datetime import datetime
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from django.conf import settings from django.conf import settings
from common.util.time import now as now_with_tz
def model_counts(request): def model_counts(request):
@ -18,16 +19,30 @@ def model_counts(request):
def add_session(request): def add_session(request):
context = {} context = {}
now = datetime.now() now = now_with_tz()
initial = {"timestamp_start": now, "timestamp_end": now} initial = {"timestamp_start": now}
form = SessionForm(request.POST or None, initial=initial) form = SessionForm(request.POST or None, initial=initial)
if form.is_valid(): if form.is_valid():
form.save() form.save()
return redirect("list_sessions")
context["form"] = form context["form"] = form
return render(request, "add_session.html", context) 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): def list_sessions(request, purchase_id=None):
context = {} context = {}
@ -54,6 +69,7 @@ def add_purchase(request):
form = PurchaseForm(request.POST or None, initial=initial) form = PurchaseForm(request.POST or None, initial=initial)
if form.is_valid(): if form.is_valid():
form.save() form.save()
return redirect("index")
context["form"] = form context["form"] = form
context["title"] = "Add New Purchase" context["title"] = "Add New Purchase"
@ -65,6 +81,7 @@ def add_game(request):
form = GameForm(request.POST or None) form = GameForm(request.POST or None)
if form.is_valid(): if form.is_valid():
form.save() form.save()
return redirect("index")
context["form"] = form context["form"] = form
context["title"] = "Add New Game" context["title"] = "Add New Game"
@ -76,6 +93,7 @@ def add_platform(request):
form = PlatformForm(request.POST or None) form = PlatformForm(request.POST or None)
if form.is_valid(): if form.is_valid():
form.save() form.save()
return redirect("index")
context["form"] = form context["form"] = form
context["title"] = "Add New Platform" context["title"] = "Add New Platform"