diff --git a/PROJECT.org b/PROJECT.org index 0ead160..d332ac5 100644 --- a/PROJECT.org +++ b/PROJECT.org @@ -93,7 +93,7 @@ fetching and simple saving. :LOGBOOK: CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20 :END: -* Backlog [4/18] :vrobbler:project:personal: +* Backlog [5/18] :vrobbler:project:personal: ** TODO [#C] Add sentiment parsing for Scrobbles with notes :vrobbler:project:scrobbles:sentiment: :PROPERTIES: :ID: 37781d6a-f3b0-48b2-bf98-33c2c791cf85 @@ -489,7 +489,7 @@ whatever time KoReader reports, we need to know, given the date and the user profile's historic timezone, how many hours to adjust the KoReader time to get to GMT to save it in the database. -** TODO [#B] Can we show a graph of all past Weigh-in tasks :scale:tasks:graphs:javascript: +** DONE [#B] Can we show a graph of all past Weigh-in tasks :scale:tasks:graphs:javascript: :PROPERTIES: :ID: ae499d87-03bf-4e48-9b2c-1a421a46af11 :END: @@ -503,6 +503,10 @@ The graph would contain all Weigh-in scrobbles for that user, no matter which date is being viewed, and the highlighted value on the graph would be the date being viewed. +Probably could use something like chart.js although maybe that's too heavy? + +And can we have each metric overlayed on the same graph? + ** DONE [#B] When viewing scrobbles by tag, sum the total time :scrobbles:tags: :PROPERTIES: :ID: d51f23df-c2c5-4e1a-b000-67c89032af02 diff --git a/vrobbler/apps/tasks/views.py b/vrobbler/apps/tasks/views.py index dcecad5..989ec88 100644 --- a/vrobbler/apps/tasks/views.py +++ b/vrobbler/apps/tasks/views.py @@ -1,5 +1,6 @@ import logging +import pendulum from django.contrib import messages from django.http import HttpResponseRedirect from django.urls import reverse_lazy @@ -8,6 +9,7 @@ from rest_framework import status from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response +from scrobbles.models import Scrobble from scrobbles.views import ScrobbleableDetailView, ScrobbleableListView from tasks.models import Task @@ -23,6 +25,72 @@ class TaskListView(ScrobbleableListView): class TaskDetailView(ScrobbleableDetailView): model = Task + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + if self.object.title != "Weigh-in": + return ctx + + scrobbles = list( + Scrobble.objects.filter( + user=self.request.user, + task=self.object, + log__weight__isnull=False, + ).order_by("timestamp") + ) + if not scrobbles: + return ctx + + labels = [] + weight_data = [] + body_fat_data = [] + bmi_data = [] + for s in scrobbles: + ts = s.timestamp + if isinstance(ts, str): + ts = pendulum.parse(ts) + labels.append(ts.strftime("%Y-%m-%d")) + log = s.log if isinstance(s.log, dict) else {} + raw_weight = log.get("weight") + weight_data.append( + float(raw_weight) if raw_weight is not None else None + ) + raw_bf = log.get("body_fat") + body_fat_data.append( + float(raw_bf) if raw_bf is not None else None + ) + raw_bmi = log.get("bmi") + bmi_data.append( + float(raw_bmi) if raw_bmi is not None else None + ) + + ctx["weighin_chart"] = { + "labels": labels, + "datasets": [ + { + "label": "Weight", + "data": weight_data, + "borderColor": "#4bc0c0", + "fill": False, + "yAxisID": "y", + }, + { + "label": "Body Fat %", + "data": body_fat_data, + "borderColor": "#ff6384", + "fill": False, + "yAxisID": "y1", + }, + { + "label": "BMI", + "data": bmi_data, + "borderColor": "#36a2eb", + "fill": False, + "yAxisID": "y2", + }, + ], + } + return ctx + @api_view(["GET"]) @permission_classes([IsAuthenticated]) diff --git a/vrobbler/templates/tasks/task_detail.html b/vrobbler/templates/tasks/task_detail.html index e5798fd..05dd1ea 100644 --- a/vrobbler/templates/tasks/task_detail.html +++ b/vrobbler/templates/tasks/task_detail.html @@ -2,6 +2,7 @@ {% load mathfilters %} {% load static %} {% load naturalduration %} +{% load l10n %} {% block title %}{{object.title}}{% endblock %} @@ -39,6 +40,15 @@
+ +{% if weighin_chart %} +{{scrobbles.count}} scrobbles
@@ -94,3 +104,61 @@ {% endif %}