Compare commits
5 Commits
61d2e65d83
...
4b45127335
Author | SHA1 | Date |
---|---|---|
Lukáš Kucharczyk | 4b45127335 | |
Lukáš Kucharczyk | b8a15e43db | |
Lukáš Kucharczyk | a1309c3738 | |
Lukáš Kucharczyk | 12cc9025a0 | |
Lukáš Kucharczyk | 6fe960bc04 |
|
@ -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
|
||||||
|
|
|
@ -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" ]
|
|
@ -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))
|
|
@ -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 (
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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 %}
|
|
@ -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(
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue