aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Schadt <kingdread@gmx.de>2026-08-12 22:17:31 +0200
committerDaniel Schadt <kingdread@gmx.de>2026-08-12 22:17:31 +0200
commit88411c91f83614a3918c2a553e2592862c147175 (patch)
tree72de1409d70bc90a52f4ba911ae5108cb1ac6624
parentef7d9dc50f03c60163215ab47664aa288272d435 (diff)
downloadfietsboek-88411c91f83614a3918c2a553e2592862c147175.tar.gz
fietsboek-88411c91f83614a3918c2a553e2592862c147175.tar.bz2
fietsboek-88411c91f83614a3918c2a553e2592862c147175.zip
add year-track-activity overview
-rw-r--r--asset-sources/theme.scss31
-rw-r--r--babel.cfg1
-rw-r--r--fietsboek/locale/de/LC_MESSAGES/messages.mobin20324 -> 20548 bytes
-rw-r--r--fietsboek/locale/de/LC_MESSAGES/messages.po26
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.mobin19139 -> 19355 bytes
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.po26
-rw-r--r--fietsboek/locale/fietslog.pot26
-rw-r--r--fietsboek/static/theme.css41
-rw-r--r--fietsboek/static/theme.css.map2
-rw-r--r--fietsboek/summaries.py101
-rw-r--r--fietsboek/templates/profile_overview.jinja229
-rw-r--r--fietsboek/views/profile.py5
12 files changed, 252 insertions, 36 deletions
diff --git a/asset-sources/theme.scss b/asset-sources/theme.scss
index ba05782..cdb4923 100644
--- a/asset-sources/theme.scss
+++ b/asset-sources/theme.scss
@@ -271,6 +271,37 @@ strong {
}
}
+.contributions {
+ tbody td.contribs-cell {
+ div {
+ border: 2px solid rgba(0, 0, 0, 0.175);
+ border-radius: calc(0.375rem - (1px));
+
+ width: 1em;
+ height: 1em;
+ }
+ }
+
+ .contribs-length-0 div {
+ background-color: #FFFFFF;
+ }
+ .contribs-length-1 div {
+ background-color: #8FF0A4;
+ }
+ .contribs-length-2 div {
+ background-color: #75DD95;
+ }
+ .contribs-length-3 div {
+ background-color: #5BC987;
+ }
+ .contribs-length-4 div {
+ background-color: #40B678;
+ }
+ .contribs-length-5 div {
+ background-color: #26A269;
+ }
+}
+
.browse-summary {
font-variant: lining-nums;
}
diff --git a/babel.cfg b/babel.cfg
index 6de4aaa..650a00a 100644
--- a/babel.cfg
+++ b/babel.cfg
@@ -4,3 +4,4 @@ jinja2 = jinja2.ext:babel_extract
[jinja2: **.jinja2]
[python: **.py]
+keywords = _ pluralize:1,2
diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.mo b/fietsboek/locale/de/LC_MESSAGES/messages.mo
index 356be25..4ffa423 100644
--- a/fietsboek/locale/de/LC_MESSAGES/messages.mo
+++ b/fietsboek/locale/de/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.po b/fietsboek/locale/de/LC_MESSAGES/messages.po
index 9367d28..8342327 100644
--- a/fietsboek/locale/de/LC_MESSAGES/messages.po
+++ b/fietsboek/locale/de/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2026-01-03 19:25+0100\n"
+"POT-Creation-Date: 2026-08-12 22:13+0200\n"
"PO-Revision-Date: 2022-07-02 17:35+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
@@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.17.0\n"
+"Generated-By: Babel 2.18.0\n"
#: fietsboek/actions.py:266
msgid "email.verify_mail.subject"
@@ -61,6 +61,12 @@ msgstr "Maximalgeschwindigkeit"
msgid "pdf.table.avg_speed"
msgstr "Durchschnittsgeschwindigkeit"
+#: fietsboek/summaries.py:384
+msgid "page.profile.contribution_card.tooltip"
+msgid_plural "page.profile.contribution_card.tooltips"
+msgstr[0] "${count} Strecke am ${date}"
+msgstr[1] "${count} Strecken am ${date}"
+
#: fietsboek/util.py:299
msgid "password_constraint.mismatch"
msgstr "Passwörter stimmen nicht überein"
@@ -1008,27 +1014,31 @@ msgstr "durchschnittliche Geschwindigkeit"
msgid "page.profile.number_of_tracks"
msgstr "Anzahl der Strecken"
-#: fietsboek/templates/profile_overview.jinja2:113
+#: fietsboek/templates/profile_overview.jinja2:112
+msgid "page.profile.contribution_card.title"
+msgstr "Streckenaktivität"
+
+#: fietsboek/templates/profile_overview.jinja2:142
msgid "page.profile.longest_distance_track"
msgstr "Weiteste Strecke"
-#: fietsboek/templates/profile_overview.jinja2:118
+#: fietsboek/templates/profile_overview.jinja2:147
msgid "page.profile.shortest_distance_track"
msgstr "Kürzeste Strecke"
-#: fietsboek/templates/profile_overview.jinja2:123
+#: fietsboek/templates/profile_overview.jinja2:152
msgid "page.profile.longest_duration_track"
msgstr "Am Längsten Dauernde Strecke"
-#: fietsboek/templates/profile_overview.jinja2:128
+#: fietsboek/templates/profile_overview.jinja2:157
msgid "page.profile.shortest_duration_track"
msgstr "Am Kürzesten Dauernde Strecke"
-#: fietsboek/templates/profile_overview.jinja2:149
+#: fietsboek/templates/profile_overview.jinja2:178
msgid "page.profile.heatmap"
msgstr "Heatmap"
-#: fietsboek/templates/profile_overview.jinja2:157
+#: fietsboek/templates/profile_overview.jinja2:186
msgid "page.profile.tilehunt"
msgstr "Kacheljäger"
diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.mo b/fietsboek/locale/en/LC_MESSAGES/messages.mo
index f4b7bbf..39eabb5 100644
--- a/fietsboek/locale/en/LC_MESSAGES/messages.mo
+++ b/fietsboek/locale/en/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.po b/fietsboek/locale/en/LC_MESSAGES/messages.po
index d52960b..a8e6c0c 100644
--- a/fietsboek/locale/en/LC_MESSAGES/messages.po
+++ b/fietsboek/locale/en/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2026-01-03 19:25+0100\n"
+"POT-Creation-Date: 2026-08-12 22:13+0200\n"
"PO-Revision-Date: 2023-04-03 20:42+0200\n"
"Last-Translator: \n"
"Language: en\n"
@@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.17.0\n"
+"Generated-By: Babel 2.18.0\n"
#: fietsboek/actions.py:266
msgid "email.verify_mail.subject"
@@ -61,6 +61,12 @@ msgstr "Max Speed"
msgid "pdf.table.avg_speed"
msgstr "Average Speed"
+#: fietsboek/summaries.py:384
+msgid "page.profile.contribution_card.tooltip"
+msgid_plural "page.profile.contribution_card.tooltips"
+msgstr[0] "${count} track on ${date}"
+msgstr[1] "${count} tracks on ${date}"
+
#: fietsboek/util.py:299
msgid "password_constraint.mismatch"
msgstr "Passwords don't match"
@@ -1002,27 +1008,31 @@ msgstr "Average Speed"
msgid "page.profile.number_of_tracks"
msgstr "Number of tracks"
-#: fietsboek/templates/profile_overview.jinja2:113
+#: fietsboek/templates/profile_overview.jinja2:112
+msgid "page.profile.contribution_card.title"
+msgstr "Track Activity"
+
+#: fietsboek/templates/profile_overview.jinja2:142
msgid "page.profile.longest_distance_track"
msgstr "Longest Track"
-#: fietsboek/templates/profile_overview.jinja2:118
+#: fietsboek/templates/profile_overview.jinja2:147
msgid "page.profile.shortest_distance_track"
msgstr "Shortest Track"
-#: fietsboek/templates/profile_overview.jinja2:123
+#: fietsboek/templates/profile_overview.jinja2:152
msgid "page.profile.longest_duration_track"
msgstr "Most Time-Consuming Track"
-#: fietsboek/templates/profile_overview.jinja2:128
+#: fietsboek/templates/profile_overview.jinja2:157
msgid "page.profile.shortest_duration_track"
msgstr "Quickest Track"
-#: fietsboek/templates/profile_overview.jinja2:149
+#: fietsboek/templates/profile_overview.jinja2:178
msgid "page.profile.heatmap"
msgstr "Heat Map"
-#: fietsboek/templates/profile_overview.jinja2:157
+#: fietsboek/templates/profile_overview.jinja2:186
msgid "page.profile.tilehunt"
msgstr "Tilehunt"
diff --git a/fietsboek/locale/fietslog.pot b/fietsboek/locale/fietslog.pot
index 50a43a0..7f7e325 100644
--- a/fietsboek/locale/fietslog.pot
+++ b/fietsboek/locale/fietslog.pot
@@ -8,14 +8,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2026-01-03 19:25+0100\n"
+"POT-Creation-Date: 2026-08-12 22:13+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.17.0\n"
+"Generated-By: Babel 2.18.0\n"
#: fietsboek/actions.py:266
msgid "email.verify_mail.subject"
@@ -57,6 +57,12 @@ msgstr ""
msgid "pdf.table.avg_speed"
msgstr ""
+#: fietsboek/summaries.py:384
+msgid "page.profile.contribution_card.tooltip"
+msgid_plural "page.profile.contribution_card.tooltips"
+msgstr[0] ""
+msgstr[1] ""
+
#: fietsboek/util.py:299
msgid "password_constraint.mismatch"
msgstr ""
@@ -994,27 +1000,31 @@ msgstr ""
msgid "page.profile.number_of_tracks"
msgstr ""
-#: fietsboek/templates/profile_overview.jinja2:113
+#: fietsboek/templates/profile_overview.jinja2:112
+msgid "page.profile.contribution_card.title"
+msgstr ""
+
+#: fietsboek/templates/profile_overview.jinja2:142
msgid "page.profile.longest_distance_track"
msgstr ""
-#: fietsboek/templates/profile_overview.jinja2:118
+#: fietsboek/templates/profile_overview.jinja2:147
msgid "page.profile.shortest_distance_track"
msgstr ""
-#: fietsboek/templates/profile_overview.jinja2:123
+#: fietsboek/templates/profile_overview.jinja2:152
msgid "page.profile.longest_duration_track"
msgstr ""
-#: fietsboek/templates/profile_overview.jinja2:128
+#: fietsboek/templates/profile_overview.jinja2:157
msgid "page.profile.shortest_duration_track"
msgstr ""
-#: fietsboek/templates/profile_overview.jinja2:149
+#: fietsboek/templates/profile_overview.jinja2:178
msgid "page.profile.heatmap"
msgstr ""
-#: fietsboek/templates/profile_overview.jinja2:157
+#: fietsboek/templates/profile_overview.jinja2:186
msgid "page.profile.tilehunt"
msgstr ""
diff --git a/fietsboek/static/theme.css b/fietsboek/static/theme.css
index 65e6881..aa9fe1b 100644
--- a/fietsboek/static/theme.css
+++ b/fietsboek/static/theme.css
@@ -80,14 +80,6 @@ strong {
flex-wrap: wrap;
}
-.track-image-preview {
- position: relative;
- width: min(317px, 100%);
- height: 178.3125px;
- border: 1px solid grey;
- margin-right: 5px;
- margin-bottom: 5px;
-}
.track-image-preview .delete-image {
position: absolute;
z-index: 5;
@@ -120,6 +112,14 @@ strong {
left: 50%;
transform: translate(-50%, -50%);
}
+.track-image-preview {
+ position: relative;
+ width: min(317px, 100%);
+ height: 178.3125px;
+ border: 1px solid grey;
+ margin-right: 5px;
+ margin-bottom: 5px;
+}
.track-description img {
max-width: 100%;
@@ -259,6 +259,31 @@ strong {
color: inherit;
}
+.contributions tbody td.contribs-cell div {
+ border: 2px solid rgba(0, 0, 0, 0.175);
+ border-radius: calc(0.375rem - 1px);
+ width: 1em;
+ height: 1em;
+}
+.contributions .contribs-length-0 div {
+ background-color: #FFFFFF;
+}
+.contributions .contribs-length-1 div {
+ background-color: #8FF0A4;
+}
+.contributions .contribs-length-2 div {
+ background-color: #75DD95;
+}
+.contributions .contribs-length-3 div {
+ background-color: #5BC987;
+}
+.contributions .contribs-length-4 div {
+ background-color: #40B678;
+}
+.contributions .contribs-length-5 div {
+ background-color: #26A269;
+}
+
.browse-summary {
font-variant: lining-nums;
}
diff --git a/fietsboek/static/theme.css.map b/fietsboek/static/theme.css.map
index 3ddb9bc..6c87d3e 100644
--- a/fietsboek/static/theme.css.map
+++ b/fietsboek/static/theme.css.map
@@ -1 +1 @@
-{"version":3,"sourceRoot":"","sources":["../../asset-sources/theme.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EAqCE;EACA;EACA;EACA;EACA;EACA;;AAzCA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAWJ;EACI;;;AAGJ;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;AAEF;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;EACA;;AACA;EACE;;AAIJ;EACE;EACA;;;AAIJ;EACE;;;AAGF;AACA;EACE;;;AAGF;EACE;EACA;;AAEA;EAJF;IAKI,eACE;;;AAIJ;EACE;;AAGF;EACE;;AAEA;EAHF;IAII;;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;;AAIJ;EACE;;;AAGF;AACA;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE","file":"theme.css"} \ No newline at end of file
+{"version":3,"sourceRoot":"","sources":["../../asset-sources/theme.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAIA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAlCJ;EAqCE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACI;;;AAGJ;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;AAEF;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;EACA;;AACA;EACE;;AAIJ;EACE;EACA;;;AAMA;EACE;EACA;EAEA;EACA;;AAIJ;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;;AAIJ;EACE;;;AAGF;AACA;EACE;;;AAGF;EACE;EACA;;AAEA;EAJF;IAKI,eACE;;;AAIJ;EACE;;AAGF;EACE;;AAEA;EAHF;IAII;;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;;AAIJ;EACE;;;AAGF;AACA;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE","file":"theme.css"} \ No newline at end of file
diff --git a/fietsboek/summaries.py b/fietsboek/summaries.py
index 0bee6ca..aa8a84c 100644
--- a/fietsboek/summaries.py
+++ b/fietsboek/summaries.py
@@ -2,11 +2,17 @@
import datetime
from dataclasses import dataclass
-from typing import Optional
+from typing import Optional, Iterator
+
+from babel.dates import format_date
+from pyramid.i18n import Localizer
+from pyramid.i18n import TranslationString as _
from . import util
from .models.track import TrackWithMetadata
+ONE_WEEK = datetime.timedelta(days=7)
+
class Summary:
"""A summary of a user's tracks.
@@ -294,4 +300,95 @@ class CumulativeStats:
return util.round_to_seconds((self.moving_time + self.stopped_time) / self.count)
-__all__ = ["Summary", "YearSummary", "MonthSummary", "CumulativeStats"]
+class YearContributions:
+ """Github-contributions-tracker-like overview for tracks."""
+
+ def __init__(self, end_date: datetime.date):
+ self.end_date = end_date
+ self.start_date = end_date - 52 * ONE_WEEK
+ # Make sure the start of the range is on a Monday
+ self.start_date -= datetime.timedelta(days=end_date.weekday())
+ assert (self.start_date - self.end_date).days <= 53 * 7
+
+ self.tracks = {}
+
+ def add(self, track: TrackWithMetadata):
+ date = track.date.date()
+ if date >= self.start_date and date <= self.end_date:
+ self.tracks.setdefault(date, []).append(track)
+
+ def rows(self) -> Iterator[Iterator[DayContributions]]:
+ for weekday in range(7):
+ row = []
+ day = self.start_date + datetime.timedelta(days=weekday)
+ while day <= self.end_date:
+ row.append(DayContributions(day, self.tracks.get(day, [])))
+ day += ONE_WEEK
+ yield row
+
+ def header_row(self, localizer: Localizer) -> Iterator[str | None]:
+ locale = localizer.locale_name
+ day = self.start_date
+ while day <= self.end_date:
+ diq = day + datetime.timedelta(days=6)
+ if diq.day <= 7:
+ yield format_date(diq, "MMM", locale=locale)
+ day += 3 * ONE_WEEK
+ else:
+ yield None
+ day += ONE_WEEK
+
+
+class DayContributions:
+ """A single cell (representing one day) for the YearContributions."""
+
+ def __init__(self, day: datetime.date, tracks: list[TrackWithMetadata]):
+ self.day = day
+ self.tracks = tracks
+
+ def total_length(self) -> float:
+ """The total length of all tracks on this day, in meters."""
+ return sum(track.length for track in self.tracks)
+
+ def count(self) -> int:
+ """The number of tracks in this day."""
+ return len(self.tracks)
+
+ def cell_style(self) -> str:
+ """Returns the CSS style that should be applied to the cell.
+
+ :return: The CSS class.
+ """
+ length = self.total_length() / 1000
+ # Arbitrary cutoffs, should maybe be the same as for the calendar
+ if length == 0:
+ return "contribs-length-0"
+ if length <= 25:
+ return "contribs-length-1"
+ if length <= 50:
+ return "contribs-length-2"
+ if length <= 75:
+ return "contribs-length-3"
+ if length <= 100:
+ return "contribs-length-4"
+ return "contribs-length-5"
+
+ def tooltip(self, localizer: Localizer) -> str:
+ """Generates the tooltip for the cell, localized to the given language.
+
+ :param localizer: The localizer to use.
+ :return: The tooltip text.
+ """
+ formatted_date = format_date(self.day, locale=localizer.locale_name)
+ return localizer.pluralize(
+ "page.profile.contribution_card.tooltip",
+ "page.profile.contribution_card.tooltips",
+ self.count(),
+ mapping={
+ "count": self.count(),
+ "date": formatted_date,
+ },
+ )
+
+
+__all__ = ["Summary", "YearSummary", "MonthSummary", "CumulativeStats", "YearContributions"]
diff --git a/fietsboek/templates/profile_overview.jinja2 b/fietsboek/templates/profile_overview.jinja2
index eaa10c6..f563ad9 100644
--- a/fietsboek/templates/profile_overview.jinja2
+++ b/fietsboek/templates/profile_overview.jinja2
@@ -109,6 +109,35 @@
</tr>
</table>
+<h2>{{ _("page.profile.contribution_card.title") }}</h2>
+<div class="d-flex justify-content-center">
+ <table class="contributions">
+ <thead>
+ <tr>
+ {% for cell in year_contributions.header_row(request.localizer) %}
+ {% if cell is none %}
+ <th></th>
+ {% else %}
+ <th colspan="3" class="text-muted">{{ cell }}</th>
+ {% endif %}
+ {% endfor %}
+ </tr>
+ </thead>
+ <tbody>
+ {% for row in year_contributions.rows() %}
+ <tr>
+ {% for cell in row %}
+ <td class="contribs-cell {{ cell.cell_style() }}">
+ <!-- Use a div because border-radius does not work on td -->
+ <div data-bs-toggle="tooltip" title="{{ cell.tooltip(request.localizer) }}"></div>
+ </td>
+ {% endfor %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+</div>
+
{% if total.longest_distance_track %}
<h2>{{ _("page.profile.longest_distance_track") }}</h2>
{{ render_track_card(total.longest_distance_track) }}
diff --git a/fietsboek/views/profile.py b/fietsboek/views/profile.py
index 6d671b9..414dfae 100644
--- a/fietsboek/views/profile.py
+++ b/fietsboek/views/profile.py
@@ -16,7 +16,7 @@ from sqlalchemy.orm import aliased
from .. import models, util
from ..data import UserDataDir
from ..models.track import TrackType, TrackWithMetadata
-from ..summaries import CumulativeStats, Summary
+from ..summaries import CumulativeStats, Summary, YearContributions
# A well-made transparent tile is actually pretty small (only 116 bytes), which
# is even smaller than our HTTP 404 page. So not only is it more efficient
@@ -49,6 +49,7 @@ LOGGER = logging.getLogger(__name__)
def profile_data(request: Request) -> dict:
"""Retrieves the profile data for the given request."""
total = CumulativeStats()
+ year_contribs = YearContributions(datetime.date.today() - datetime.timedelta(days=365))
query = request.context.all_tracks_query()
query = select(aliased(models.Track, query)).where(query.c.type == TrackType.ORGANIC)
@@ -56,6 +57,7 @@ def profile_data(request: Request) -> dict:
for track in request.dbsession.execute(query).scalars():
meta = TrackWithMetadata(track)
total.add(meta)
+ year_contribs.add(meta)
total.moving_time = util.round_to_seconds(total.moving_time)
total.stopped_time = util.round_to_seconds(total.stopped_time)
@@ -79,6 +81,7 @@ def profile_data(request: Request) -> dict:
"mps_to_kph": util.mps_to_kph,
"heatmap_url": heatmap_url,
"tilehunt_url": tilehunt_url,
+ "year_contributions": year_contribs,
}