Compare commits
	
		
			5 Commits
		
	
	
		
			61d2e65d83
			...
			4b45127335
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4b45127335 | |||
| b8a15e43db | |||
| a1309c3738 | |||
| 12cc9025a0 | |||
| 6fe960bc04 | 
| @ -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 | ||||
|  | ||||
| @ -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" ] | ||||
							
								
								
									
										0
									
								
								src/web/common/util/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/web/common/util/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										7
									
								
								src/web/common/util/time.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/web/common/util/time.py
									
									
									
									
									
										Normal 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)) | ||||
| @ -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.duration_manual == None: | ||||
|             if self.timestamp_end == None or self.timestamp_start == None: | ||||
|                 return 0 | ||||
|             else: | ||||
|                 value = self.duration_manual | ||||
|                 value = self.timestamp_end - self.timestamp_start | ||||
|         else: | ||||
|             value = self.timestamp_end - self.timestamp_start | ||||
|             value = self.duration_manual | ||||
|         return value.total_seconds() | ||||
|  | ||||
|     def duration_formatted(self): | ||||
| @ -55,10 +60,13 @@ class Session(models.Model): | ||||
|         if seconds == 0: | ||||
|             return seconds | ||||
|         hours, remainder = divmod(seconds, 3600) | ||||
|         minutes = remainder % 60 | ||||
|         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}" | ||||
|         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}" | ||||
|  | ||||
|     def duration_any(self): | ||||
|         return ( | ||||
|  | ||||
| @ -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)); | ||||
|  | ||||
| @ -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 %} | ||||
| @ -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( | ||||
|  | ||||
| @ -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" | ||||
|  | ||||
		Reference in New Issue
	
	Block a user