Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ This MyTraL **minor** release brings:
- .

### Added
- .
- Calendar and sickness heatmaps now mark today with a yellow rectangle so the
current week and day stand out, and each sickness day's tooltip now reads like
`2026-05-25: 3x sick` instead of a bare count.

### Fixed
- .
Expand Down
4 changes: 3 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ This MyTraL **minor** release brings:
- .

### Added
- .
- Calendar and sickness heatmaps now mark today with a yellow rectangle so the
current week and day stand out, and each sickness day's tooltip now reads like
`2026-05-25: 3x sick` instead of a bare count.

### Fixed
- .
Expand Down
4 changes: 3 additions & 1 deletion mytral/static/documentation/changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,9 @@ <h3 id="changed">Changed</h3>
</ul>
<h3 id="added">Added</h3>
<ul>
<li>.</li>
<li>Calendar and sickness heatmaps now mark today with a yellow rectangle so the
current week and day stand out, and each sickness day's tooltip now reads like
<code>2026-05-25: 3x sick</code> instead of a bare count.</li>
</ul>
<h3 id="fixed">Fixed</h3>
<ul>
Expand Down
7 changes: 7 additions & 0 deletions mytral/static/mytral.css
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ div.heat_comment_only {
background-color: #4682B4;
}

/* "today" marker: a banana-yellow ring drawn inside the cell, so it is never
clipped by the scrollable calendar (even on the top row) and stays visible in
any theme */
div.heat_today {
box-shadow: inset 0 0 0 2px #ffe135;
}

/* Left menu: collapsible and styles ********************************************** */

.collapsible {
Expand Down
44 changes: 23 additions & 21 deletions mytral/templates/heatmap-calendar.html
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
{% macro heatmap_cell( data, year, week_day, week_number ) %}
{% if week_day in data.heatmap[year][week_number] %}
<td title="{{ data.heatmap[year][week_number][week_day].title }}">
<a href="/activities/date/{{ data.heatmap[year][week_number][week_day].slash_date }}">
{% if data.heatmap[year][week_number][week_day].count %}
{% if data.heatmap[year][week_number][week_day].is_active_sick %}
{% if data.heatmap[year][week_number][week_day].count_activities < 2 %}
<div class="heat_active_sick_low"></div></a></td>
{% elif data.heatmap[year][week_number][week_day].count_activities < 3 %}
<div class="heat_active_sick_medium"></div></a></td>
{% set cell = data.heatmap[year][week_number][week_day] %}
{% set today = ' heat_today' if cell.is_today else '' %}
<td title="{{ cell.title }}">
<a href="/activities/date/{{ cell.slash_date }}">
{% if cell.count %}
{% if cell.is_active_sick %}
{% if cell.count_activities < 2 %}
<div class="heat_active_sick_low{{ today }}"></div></a></td>
{% elif cell.count_activities < 3 %}
<div class="heat_active_sick_medium{{ today }}"></div></a></td>
{% else %}
<div class="heat_active_sick_high"></div></a></td>
<div class="heat_active_sick_high{{ today }}"></div></a></td>
{% endif %}
{% elif data.heatmap[year][week_number][week_day].is_sick %}
<div class="heat_sick"></div></a></td>
{% elif data.heatmap[year][week_number][week_day].is_active %}
{% if data.heatmap[year][week_number][week_day].count_activities < 2 %}
<div class="heat_y_low"></div></a></td>
{% elif data.heatmap[year][week_number][week_day].count_activities < 3 %}
<div class="heat_y_medium"></div></a></td>
{% elif cell.is_sick %}
<div class="heat_sick{{ today }}"></div></a></td>
{% elif cell.is_active %}
{% if cell.count_activities < 2 %}
<div class="heat_y_low{{ today }}"></div></a></td>
{% elif cell.count_activities < 3 %}
<div class="heat_y_medium{{ today }}"></div></a></td>
{% else %}
<div class="heat_y_high"></div></a></td>
<div class="heat_y_high{{ today }}"></div></a></td>
{% endif %}
{% elif data.heatmap[year][week_number][week_day].has_comment %}
<div class="heat_comment_only"></div></a></td>
{% elif cell.has_comment %}
<div class="heat_comment_only{{ today }}"></div></a></td>
{% else %}
<div class="heat_n"></div></a></td>
<div class="heat_n{{ today }}"></div></a></td>
{% endif %}
{% else %}
<div class="heat_n"></div></a></td>
<div class="heat_n{{ today }}"></div></a></td>
{% endif %}
{% else %}
<td><div class="heat_na"></div></td>
Expand Down
64 changes: 27 additions & 37 deletions mytral/templates/insight-sickness-heatmap.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
{% macro sickness_cell( data, year, week_day, week_number ) %}
{% if week_day in data.heatmap[year][week_number] %}
{% set cell = data.heatmap[year][week_number][week_day] %}
{% set date_parts = cell.slash_date.split('/') %}
{% set today = ' heat_today' if cell.is_today else '' %}
{% set iso_date = "%04d-%02d-%02d"|format(cell.year, cell.month, cell.day) %}
<td title="{{ iso_date }}{% if cell.count %}: {{ cell.count }}x sick{% endif %}">
<a href="/activities/month-day/{{ date_parts[1] }}/{{ date_parts[2] }}">
{% if cell.count %}
{% if cell.count < 2 %}
<div class="heat_sick_low{{ today }}"></div></a></td>
{% elif cell.count < 3 %}
<div class="heat_sick_medium{{ today }}"></div></a></td>
{% else %}
<div class="heat_sick{{ today }}"></div></a></td>
{% endif %}
{% else %}
<div class="heat_n{{ today }}"></div></a></td>
{% endif %}
{% else %}
<td><div class="heat_na"></div></td>
{% endif %}
{% endmacro %}

{% extends "layout.html" %}
{% block content %}
{% if no_data %}
Expand Down Expand Up @@ -58,7 +82,7 @@ <h2 class="page-title">Sickness Heatmap</h2>
<p class="text-muted">
Data from the past years are used to create a heatmap of sicknesses. The heatmap is a calendar view where each day is colored
according to the number of sicknesses that day. The color scale is from gray (no sickness) to red (many sicknesses). The heatmap is interactive,
and you can click on a day to see the details for that date.
and you can click on a day to see the details for that date. Today is marked with a banana-yellow ring.
Comment thread
Copilot marked this conversation as resolved.
Outdated
</p>
</div>
</div>
Expand All @@ -75,49 +99,15 @@ <h2 class="page-title">Sickness Heatmap</h2>
{% for week_number in data.heatmap[year] %}
<tr>
{% for week_day in range(7) %}
{% if week_day in data.heatmap[year][week_number] %}
{% set date_parts = data.heatmap[year][week_number][week_day].slash_date.split('/') %}
<td title="{{ data.heatmap[year][week_number][week_day].count or 0 }}">
<a href="/activities/month-day/{{ date_parts[1] }}/{{ date_parts[2] }}">
{% if data.heatmap[year][week_number][week_day].count %}
{% if data.heatmap[year][week_number][week_day].count < 2 %}
<div class="heat_sick_low"></div></a></td>
{% elif data.heatmap[year][week_number][week_day].count < 3 %}
<div class="heat_sick_medium"></div></a></td>
{% else %}
<div class="heat_sick"></div></a></td>
{% endif %}
{% else %}
<div class="heat_n"></div></a></td>
{% endif %}
{% else %}
<td><div class="heat_na"></div></td>
{% endif %}
{{ sickness_cell( data, year, week_day, week_number ) }}
{% endfor %}
</tr>
{% endfor %}
{% else %}
{% for week_day in range(7) %}
<tr>
{% for week_number in data.heatmap[year] %}
{% if week_day in data.heatmap[year][week_number] %}
{% set date_parts = data.heatmap[year][week_number][week_day].slash_date.split('/') %}
<td title="{{ data.heatmap[year][week_number][week_day].count or 0 }}">
<a href="/activities/month-day/{{ date_parts[1] }}/{{ date_parts[2] }}">
{% if data.heatmap[year][week_number][week_day].count %}
{% if data.heatmap[year][week_number][week_day].count < 2 %}
<div class="heat_sick_low"></div></a></td>
{% elif data.heatmap[year][week_number][week_day].count < 3 %}
<div class="heat_sick_medium"></div></a></td>
{% else %}
<div class="heat_sick"></div></a></td>
{% endif %}
{% else %}
<div class="heat_n"></div></a></td>
{% endif %}
{% else %}
<td><div class="heat_na"></div></td>
{% endif %}
{{ sickness_cell( data, year, week_day, week_number ) }}
{% endfor %}
</tr>
{% endfor %}
Expand Down
15 changes: 15 additions & 0 deletions mytral/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ def count_activities(self) -> int:
def slash_date(self) -> str:
return f"{self.year}/{self.month}/{self.day}"

@property
def is_today(self) -> bool:
"""Whether this cell is today's date.

The sickness heatmap collapses every year onto the current calendar
(cells carry the current year), while the activity heatmap keeps the
real year per cell, so a full-date match marks exactly one cell.
"""
today = datetime.date.today()
return (self.year, self.month, self.day) == (
today.year,
today.month,
today.day,
)

@property
def is_active_sick(self):
return self.is_active and self.is_sick
Expand Down
77 changes: 77 additions & 0 deletions tests/test_sickness_heatmap_today.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# MyTraL: my trailing log
#
# Copyright (C) 2015-2026 Martin Dvorak <martin.dvorak@mindforger.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Tests for the "today" marker on the sickness calendar heatmap."""

import datetime

import pytest

from mytral import settings
from mytral import views


def _activity_types() -> settings.UserActivityTypes:
return settings.UserActivityTypes(activity_types=[])


@pytest.mark.mytral
def test_cell_is_today_matches_current_month_and_day():
# GIVEN a heatmap cell for today's month and day
today = datetime.date.today()
cell = views.CalendarHeatmap.Cell(
year=today.year,
month=today.month,
day=today.day,
activity_types=_activity_types(),
)

# WHEN checking whether the cell maps to today
# THEN it does
assert cell.is_today is True

Comment thread
dvorka marked this conversation as resolved.

@pytest.mark.mytral
def test_cell_is_today_false_for_other_day():
# GIVEN a heatmap cell for a day that is not today
today = datetime.date.today()
other = today + datetime.timedelta(days=1)
cell = views.CalendarHeatmap.Cell(
year=other.year,
month=other.month,
day=other.day,
activity_types=_activity_types(),
)

# WHEN checking whether the cell maps to today
# THEN it does not
assert cell.is_today is False
Comment thread
dvorka marked this conversation as resolved.


@pytest.mark.mytral
def test_cell_is_today_false_for_same_day_different_year():
# GIVEN a cell with today's month and day but a different year
today = datetime.date.today()
cell = views.CalendarHeatmap.Cell(
year=today.year - 3,
month=today.month,
day=today.day,
activity_types=_activity_types(),
)

# WHEN checking whether the cell maps to today
# THEN it does not, because the activity heatmap keeps the real year per cell
assert cell.is_today is False
Comment thread
dvorka marked this conversation as resolved.
Loading