Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
4070b4e46e | |||
4892218c83 | |||
6b00a950ce | |||
feee9d6dac | |||
9654fb017d
|
|||
1741397ee7 | |||
da0c8d710b |
23
.drone.yml
23
.drone.yml
@ -11,15 +11,30 @@ steps:
|
|||||||
- poetry install
|
- poetry install
|
||||||
- poetry env info
|
- poetry env info
|
||||||
- poetry run pytest
|
- poetry run pytest
|
||||||
- name: build container
|
- name: build container (prod)
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings:
|
||||||
repo: registry.kucharczyk.xyz/timetracker
|
repo: registry.kucharczyk.xyz/timetracker
|
||||||
tags:
|
tags:
|
||||||
- latest
|
- latest
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- main
|
||||||
|
|
||||||
|
- name: build container (non-prod)
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
repo: registry.kucharczyk.xyz/timetracker
|
||||||
|
tags:
|
||||||
|
- ${DRONE_COMMIT_REF}
|
||||||
|
- ${DRONE_COMMIT_BRANCH}
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
exclude:
|
||||||
|
- main
|
||||||
|
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
event:
|
event:
|
||||||
- push
|
- push
|
||||||
- cron
|
- cron
|
||||||
exclude:
|
|
||||||
- pull_request
|
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,4 +5,5 @@ __pycache__
|
|||||||
node_modules
|
node_modules
|
||||||
package-lock.json
|
package-lock.json
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
static
|
static
|
||||||
|
dist/
|
@ -1,3 +1,9 @@
|
|||||||
|
## 1.0.1 / 2023-01-30 22:17+01:00
|
||||||
|
|
||||||
|
* Make it possible to edit sessions (https://git.kucharczyk.xyz/lukas/timetracker/issues/46)
|
||||||
|
* Show markers on smaller graphs to make it clearer which dates the session belong to
|
||||||
|
* Show only last 30 days on the homepage (https://git.kucharczyk.xyz/lukas/timetracker/issues/47)
|
||||||
|
|
||||||
## 1.0.0 / 2023-01-20 19:54+01:00
|
## 1.0.0 / 2023-01-20 19:54+01:00
|
||||||
|
|
||||||
* Breaking
|
* Breaking
|
||||||
|
@ -64,7 +64,7 @@ def get_chart(data, title="", xlabel="", ylabel=""):
|
|||||||
plt.switch_backend("SVG")
|
plt.switch_backend("SVG")
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
fig.set_size_inches(10, 4)
|
fig.set_size_inches(10, 4)
|
||||||
ax.plot(x, y)
|
lines = ax.plot(x, y, "-o")
|
||||||
first = x[0]
|
first = x[0]
|
||||||
last = x[-1]
|
last = x[-1]
|
||||||
difference = last - first
|
difference = last - first
|
||||||
@ -76,7 +76,11 @@ def get_chart(data, title="", xlabel="", ylabel=""):
|
|||||||
elif difference.days < 720:
|
elif difference.days < 720:
|
||||||
ax.xaxis.set_major_locator(mdates.MonthLocator())
|
ax.xaxis.set_major_locator(mdates.MonthLocator())
|
||||||
ax.xaxis.set_minor_locator(mdates.WeekdayLocator())
|
ax.xaxis.set_minor_locator(mdates.WeekdayLocator())
|
||||||
|
for line in lines:
|
||||||
|
line.set_marker("")
|
||||||
else:
|
else:
|
||||||
|
for line in lines:
|
||||||
|
line.set_marker("")
|
||||||
ax.xaxis.set_major_locator(mdates.YearLocator())
|
ax.xaxis.set_major_locator(mdates.YearLocator())
|
||||||
ax.xaxis.set_minor_locator(mdates.MonthLocator())
|
ax.xaxis.set_minor_locator(mdates.MonthLocator())
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<div class="dark:bg-gray-800 min-h-screen">
|
<div class="dark:bg-gray-800 min-h-screen">
|
||||||
<nav class="mb-4 bg-white dark:bg-gray-900 border-gray-200 rounded">
|
<nav class="mb-4 bg-white dark:bg-gray-900 border-gray-200 rounded">
|
||||||
<div class="container flex flex-wrap items-center justify-between mx-auto">
|
<div class="container flex flex-wrap items-center justify-between mx-auto">
|
||||||
<a href="{% url 'index' %}" class="flex items-center">
|
<a href="{% url 'list_sessions_recent' %}" class="flex items-center">
|
||||||
<span class="text-4xl">⌚</span>
|
<span class="text-4xl">⌚</span>
|
||||||
<span class="self-center text-xl font-semibold whitespace-nowrap text-white">Timetracker</span>
|
<span class="self-center text-xl font-semibold whitespace-nowrap text-white">Timetracker</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}Sessions{% endblock title %}
|
{% block title %}{{ title }}{% endblock title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="text-center text-xl mb-4 dark:text-slate-400">
|
<div class="text-center text-xl mb-4 dark:text-slate-400">
|
||||||
@ -62,12 +62,14 @@
|
|||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button type="button" title="Edit" class="py-1 px-2 flex justify-center items-center bg-blue-600 hover:bg-blue-700 focus:ring-blue-500 focus:ring-offset-blue-200 text-white transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 w-7 h-4 rounded-lg ">
|
<a href="{% url 'edit_session' data.id %}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
<button type="button" title="Edit" class="py-1 px-2 flex justify-center items-center bg-blue-600 hover:bg-blue-700 focus:ring-blue-500 focus:ring-offset-blue-200 text-white transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 w-7 h-4 rounded-lg ">
|
||||||
<path d="M5.433 13.917l1.262-3.155A4 4 0 017.58 9.42l6.92-6.918a2.121 2.121 0 013 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 01-.65-.65z" />
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
||||||
<path d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0010 3H4.75A2.75 2.75 0 002 5.75v9.5A2.75 2.75 0 004.75 18h9.5A2.75 2.75 0 0017 15.25V10a.75.75 0 00-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5z" />
|
<path d="M5.433 13.917l1.262-3.155A4 4 0 017.58 9.42l6.92-6.918a2.121 2.121 0 013 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 01-.65-.65z" />
|
||||||
</svg>
|
<path d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0010 3H4.75A2.75 2.75 0 002 5.75v9.5A2.75 2.75 0 004.75 18h9.5A2.75 2.75 0 0017 15.25V10a.75.75 0 00-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5z" />
|
||||||
</button>
|
</svg>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
<a href="{% url 'delete_session' data.id %}">
|
<a href="{% url 'delete_session' data.id %}">
|
||||||
<button type="button" edit="Delete" class="py-1 px-2 flex justify-center items-center bg-red-600 hover:bg-red-700 focus:ring-red-500 focus:ring-offset-blue-200 text-white transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 w-7 h-4 rounded-lg ">
|
<button type="button" edit="Delete" class="py-1 px-2 flex justify-center items-center bg-red-600 hover:bg-red-700 focus:ring-red-500 focus:ring-offset-blue-200 text-white transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 w-7 h-4 rounded-lg ">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
|
||||||
|
@ -3,7 +3,7 @@ from django.urls import path
|
|||||||
from games import views
|
from games import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.index, name="index"),
|
path("", views.list_sessions, {"filter": "recent"}, name="list_sessions_recent"),
|
||||||
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"),
|
||||||
@ -23,6 +23,7 @@ urlpatterns = [
|
|||||||
name="delete_session",
|
name="delete_session",
|
||||||
),
|
),
|
||||||
path("add-purchase/", views.add_purchase, name="add_purchase"),
|
path("add-purchase/", views.add_purchase, name="add_purchase"),
|
||||||
|
path("edit-session/<int:session_id>", views.edit_session, name="edit_session"),
|
||||||
path("list-sessions/", views.list_sessions, name="list_sessions"),
|
path("list-sessions/", views.list_sessions, name="list_sessions"),
|
||||||
path(
|
path(
|
||||||
"list-sessions/by-purchase/<int:purchase_id>",
|
"list-sessions/by-purchase/<int:purchase_id>",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from common.plots import playtime_over_time_chart
|
from common.plots import playtime_over_time_chart
|
||||||
@ -47,6 +47,18 @@ def update_session(request, session_id=None):
|
|||||||
return redirect("list_sessions")
|
return redirect("list_sessions")
|
||||||
|
|
||||||
|
|
||||||
|
def edit_session(request, session_id=None):
|
||||||
|
context = {}
|
||||||
|
session = Session.objects.get(id=session_id)
|
||||||
|
form = SessionForm(request.POST or None, instance=session)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return redirect("list_sessions")
|
||||||
|
context["title"] = "Edit Session"
|
||||||
|
context["form"] = form
|
||||||
|
return render(request, "add.html", context)
|
||||||
|
|
||||||
|
|
||||||
def start_session(request, purchase_id=None):
|
def start_session(request, purchase_id=None):
|
||||||
session = SessionForm({"purchase": purchase_id, "timestamp_start": now_with_tz()})
|
session = SessionForm({"purchase": purchase_id, "timestamp_start": now_with_tz()})
|
||||||
session.save()
|
session.save()
|
||||||
@ -61,6 +73,7 @@ def delete_session(request, session_id=None):
|
|||||||
|
|
||||||
def list_sessions(request, filter="", purchase_id="", platform_id="", game_id=""):
|
def list_sessions(request, filter="", purchase_id="", platform_id="", game_id=""):
|
||||||
context = {}
|
context = {}
|
||||||
|
context["title"] = "Sessions"
|
||||||
|
|
||||||
if filter == "purchase":
|
if filter == "purchase":
|
||||||
dataset = Session.objects.filter(purchase=purchase_id)
|
dataset = Session.objects.filter(purchase=purchase_id)
|
||||||
@ -71,6 +84,11 @@ def list_sessions(request, filter="", purchase_id="", platform_id="", game_id=""
|
|||||||
elif filter == "game":
|
elif filter == "game":
|
||||||
dataset = Session.objects.filter(purchase__game=game_id)
|
dataset = Session.objects.filter(purchase__game=game_id)
|
||||||
context["game"] = Game.objects.get(id=game_id)
|
context["game"] = Game.objects.get(id=game_id)
|
||||||
|
elif filter == "recent":
|
||||||
|
dataset = Session.objects.filter(
|
||||||
|
timestamp_start__gte=datetime.now() - timedelta(days=30)
|
||||||
|
)
|
||||||
|
context["title"] = "Last 30 days"
|
||||||
else:
|
else:
|
||||||
# by default, sort from newest to oldest
|
# by default, sort from newest to oldest
|
||||||
dataset = Session.objects.all().order_by("-timestamp_start")
|
dataset = Session.objects.all().order_by("-timestamp_start")
|
||||||
@ -127,10 +145,3 @@ def add_platform(request):
|
|||||||
context["form"] = form
|
context["form"] = form
|
||||||
context["title"] = "Add New Platform"
|
context["title"] = "Add New Platform"
|
||||||
return render(request, "add.html", context)
|
return render(request, "add.html", context)
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
context = {}
|
|
||||||
context["total_duration"] = Session().duration_sum
|
|
||||||
context["title"] = "Index"
|
|
||||||
return render(request, "index.html", context)
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "timetracker"
|
name = "timetracker"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
description = "A simple time tracker."
|
description = "A simple time tracker."
|
||||||
authors = ["Lukáš Kucharczyk <lukas@kucharczyk.xyz>"]
|
authors = ["Lukáš Kucharczyk <lukas@kucharczyk.xyz>"]
|
||||||
license = "GPL"
|
license = "GPL"
|
||||||
|
@ -19,7 +19,7 @@ from django.urls import include, path
|
|||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", RedirectView.as_view(url="/tracker/list-sessions")),
|
path("", RedirectView.as_view(url="/tracker")),
|
||||||
path("tracker/", include("games.urls")),
|
path("tracker/", include("games.urls")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user