diff --git a/dmapp/dmweb/dm.py b/dmapp/dmweb/dm.py index cd0335f..2b31846 100644 --- a/dmapp/dmweb/dm.py +++ b/dmapp/dmweb/dm.py @@ -1,17 +1,19 @@ from datetime import datetime, timedelta -from flask import Blueprint, jsonify, render_template +from flask import Blueprint, jsonify, render_template, request from .get_period_times import ( + SUPPORTED_TIMEZONES, convert_seconds, + default_timezone, get_current_task_info, get_period_totals, get_raw_switches, get_task_blocks_calendar, get_task_time_seconds, + get_timezone, get_work_period_totals, task_or_none, - timezone, ) dmbp = Blueprint("deskmeter", __name__, url_prefix="/", template_folder="templates") @@ -29,13 +31,14 @@ def calendar_view(scope="daily", year=None, month=None, day=None): """ Google Calendar-style view showing task blocks at their actual times. """ - from flask import request - task = None grid = int(request.args.get("grid", 1)) # Grid hours: 1, 3, or 6 if grid not in [1, 3, 6]: grid = 1 + tz_name = request.args.get("tz") + tz = get_timezone(tz_name) + if not year: year = datetime.today().year if not month: @@ -44,14 +47,14 @@ def calendar_view(scope="daily", year=None, month=None, day=None): day = datetime.today().day base_date = datetime(year, month, day).replace( - hour=0, minute=0, second=0, tzinfo=timezone + hour=0, minute=0, second=0, tzinfo=tz ) if scope == "daily": start = base_date end = base_date.replace(hour=23, minute=59, second=59) blocks = get_task_blocks_calendar( - start, end, task, min_block_seconds=60, grid_hours=grid + start, end, task, min_block_seconds=60, grid_hours=grid, tz=tz ) prev_date = base_date - timedelta(days=1) next_date = base_date + timedelta(days=1) @@ -61,7 +64,7 @@ def calendar_view(scope="daily", year=None, month=None, day=None): start = base_date - timedelta(days=base_date.weekday()) end = start + timedelta(days=6, hours=23, minutes=59, seconds=59) blocks = get_task_blocks_calendar( - start, end, task, min_block_seconds=300, grid_hours=grid + start, end, task, min_block_seconds=300, grid_hours=grid, tz=tz ) prev_date = start - timedelta(days=7) next_date = start + timedelta(days=7) @@ -70,20 +73,20 @@ def calendar_view(scope="daily", year=None, month=None, day=None): elif scope == "monthly": start = base_date.replace(day=1) if month == 12: - end = datetime(year + 1, 1, 1, tzinfo=timezone) - timedelta(seconds=1) + end = datetime(year + 1, 1, 1, tzinfo=tz) - timedelta(seconds=1) else: - end = datetime(year, month + 1, 1, tzinfo=timezone) - timedelta(seconds=1) + end = datetime(year, month + 1, 1, tzinfo=tz) - timedelta(seconds=1) blocks = get_task_blocks_calendar( - start, end, task, min_block_seconds=600, grid_hours=grid + start, end, task, min_block_seconds=600, grid_hours=grid, tz=tz ) if month == 1: - prev_date = datetime(year - 1, 12, 1, tzinfo=timezone) + prev_date = datetime(year - 1, 12, 1, tzinfo=tz) else: - prev_date = datetime(year, month - 1, 1, tzinfo=timezone) + prev_date = datetime(year, month - 1, 1, tzinfo=tz) if month == 12: - next_date = datetime(year + 1, 1, 1, tzinfo=timezone) + next_date = datetime(year + 1, 1, 1, tzinfo=tz) else: - next_date = datetime(year, month + 1, 1, tzinfo=timezone) + next_date = datetime(year, month + 1, 1, tzinfo=tz) days = [] current = start while current <= end: @@ -94,7 +97,7 @@ def calendar_view(scope="daily", year=None, month=None, day=None): start = base_date end = base_date.replace(hour=23, minute=59, second=59) blocks = get_task_blocks_calendar( - start, end, task, min_block_seconds=60, grid_hours=grid + start, end, task, min_block_seconds=60, grid_hours=grid, tz=tz ) prev_date = base_date - timedelta(days=1) next_date = base_date + timedelta(days=1) @@ -111,6 +114,8 @@ def calendar_view(scope="daily", year=None, month=None, day=None): next_date=next_date, days=days, grid=grid, + tz_name=tz_name, + timezones=SUPPORTED_TIMEZONES, auto_refresh=False, ) @@ -124,6 +129,9 @@ def switches_view(scope="daily", year=None, month=None, day=None): """ task = None + tz_name = request.args.get("tz") + tz = get_timezone(tz_name) + if not year: year = datetime.today().year if not month: @@ -132,7 +140,7 @@ def switches_view(scope="daily", year=None, month=None, day=None): day = datetime.today().day base_date = datetime(year, month, day).replace( - hour=0, minute=0, second=0, tzinfo=timezone + hour=0, minute=0, second=0, tzinfo=tz ) if scope == "daily": @@ -150,17 +158,17 @@ def switches_view(scope="daily", year=None, month=None, day=None): elif scope == "monthly": start = base_date.replace(day=1) if month == 12: - end = datetime(year + 1, 1, 1, tzinfo=timezone) - timedelta(seconds=1) + end = datetime(year + 1, 1, 1, tzinfo=tz) - timedelta(seconds=1) else: - end = datetime(year, month + 1, 1, tzinfo=timezone) - timedelta(seconds=1) + end = datetime(year, month + 1, 1, tzinfo=tz) - timedelta(seconds=1) if month == 1: - prev_date = datetime(year - 1, 12, 1, tzinfo=timezone) + prev_date = datetime(year - 1, 12, 1, tzinfo=tz) else: - prev_date = datetime(year, month - 1, 1, tzinfo=timezone) + prev_date = datetime(year, month - 1, 1, tzinfo=tz) if month == 12: - next_date = datetime(year + 1, 1, 1, tzinfo=timezone) + next_date = datetime(year + 1, 1, 1, tzinfo=tz) else: - next_date = datetime(year, month + 1, 1, tzinfo=timezone) + next_date = datetime(year, month + 1, 1, tzinfo=tz) else: scope = "daily" start = base_date @@ -168,7 +176,7 @@ def switches_view(scope="daily", year=None, month=None, day=None): prev_date = base_date - timedelta(days=1) next_date = base_date + timedelta(days=1) - raw_switches = get_raw_switches(start, end, task) + raw_switches = get_raw_switches(start, end, task, tz=tz) return render_template( "switches_view.html", @@ -179,6 +187,8 @@ def switches_view(scope="daily", year=None, month=None, day=None): base_date=base_date, prev_date=prev_date, next_date=next_date, + tz_name=tz_name, + timezones=SUPPORTED_TIMEZONES, auto_refresh=False, ) @@ -191,8 +201,12 @@ def index(task=None): """ task = task_or_none(task) - start = datetime.today().replace(hour=0, minute=0, second=0, tzinfo=timezone) - end = datetime.today().replace(hour=23, minute=59, second=59, tzinfo=timezone) + start = datetime.today().replace( + hour=0, minute=0, second=0, tzinfo=default_timezone + ) + end = datetime.today().replace( + hour=23, minute=59, second=59, tzinfo=default_timezone + ) rows = get_period_totals(start, end, task) @@ -230,8 +244,12 @@ def api_today(task=None): """ task = task_or_none(task) - start = datetime.today().replace(hour=0, minute=0, second=0, tzinfo=timezone) - end = datetime.today().replace(hour=23, minute=59, second=59, tzinfo=timezone) + start = datetime.today().replace( + hour=0, minute=0, second=0, tzinfo=default_timezone + ) + end = datetime.today().replace( + hour=23, minute=59, second=59, tzinfo=default_timezone + ) rows = get_period_totals(start, end, task) @@ -259,11 +277,11 @@ def oneday( task = task_or_none(task) start = datetime(2025, month, day).replace( - hour=0, minute=0, second=0, tzinfo=timezone + hour=0, minute=0, second=0, tzinfo=default_timezone ) end = datetime(2025, month, day).replace( - hour=23, minute=59, second=59, tzinfo=timezone + hour=23, minute=59, second=59, tzinfo=default_timezone ) rows = get_period_totals(start, end) @@ -274,11 +292,11 @@ def oneday( @dmbp.route("/period//") def period(start, end): start = datetime(*map(int, start.split("-"))).replace( - hour=0, minute=0, second=0, tzinfo=timezone + hour=0, minute=0, second=0, tzinfo=default_timezone ) end = datetime(*map(int, end.split("-"))).replace( - hour=23, minute=59, second=59, tzinfo=timezone + hour=23, minute=59, second=59, tzinfo=default_timezone ) rows = get_period_totals(start, end) @@ -291,8 +309,12 @@ def work(): """ Show total time used per work project for today """ - start = datetime.today().replace(hour=0, minute=0, second=0, tzinfo=timezone) - end = datetime.today().replace(hour=23, minute=59, second=59, tzinfo=timezone) + start = datetime.today().replace( + hour=0, minute=0, second=0, tzinfo=default_timezone + ) + end = datetime.today().replace( + hour=23, minute=59, second=59, tzinfo=default_timezone + ) rows = get_work_period_totals(start, end) @@ -308,9 +330,13 @@ def totals(task=None): task = task_or_none(task) - start = datetime(2020, 1, 1).replace(hour=0, minute=0, second=0, tzinfo=timezone) + start = datetime(2020, 1, 1).replace( + hour=0, minute=0, second=0, tzinfo=default_timezone + ) - end = datetime(2030, 1, 1).replace(hour=23, minute=59, second=59, tzinfo=timezone) + end = datetime(2030, 1, 1).replace( + hour=23, minute=59, second=59, tzinfo=default_timezone + ) rows = get_period_totals(start, end) diff --git a/dmapp/dmweb/templates/calendar_view.html b/dmapp/dmweb/templates/calendar_view.html index 6c13095..3d61a01 100644 --- a/dmapp/dmweb/templates/calendar_view.html +++ b/dmapp/dmweb/templates/calendar_view.html @@ -86,6 +86,7 @@ display: flex; align-items: flex-start; justify-content: flex-end; + box-sizing: border-box; } .days-grid { @@ -114,6 +115,7 @@ font-weight: bold; font-size: 11pt; color: #e0e0e0; + box-sizing: border-box; } .day-grid { @@ -137,17 +139,25 @@ color: white; overflow: hidden; cursor: pointer; - transition: all 0.2s; + transition: all 0.15s ease-out; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); border: 1px solid rgba(0, 0, 0, 0.3); } .task-block:hover { - z-index: 100; - transform: scale(1.05); + z-index: 100 !important; + overflow: visible; + min-height: fit-content; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.7); } + .task-block:hover .task-label, + .task-block:hover .task-time { + background: inherit; + white-space: nowrap; + display: inline-block; + } + .task-label { font-weight: bold; margin-bottom: 2px; @@ -157,28 +167,6 @@ font-size: 8pt; opacity: 0.9; } - - .block-tooltip { - display: none; - position: absolute; - background: #2a2a2a; - color: #e0e0e0; - padding: 8px 12px; - border-radius: 4px; - font-size: 10pt; - z-index: 1000; - white-space: nowrap; - pointer-events: none; - left: 100%; - top: 0; - margin-left: 10px; - border: 1px solid #444; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); - } - - .task-block:hover .block-tooltip { - display: block; - } {% endblock head %} {% block content %} @@ -194,25 +182,45 @@ Today Calendar Switches All Time + @@ -284,41 +292,31 @@ > {% endfor %} {% set day_blocks = [] %} {% for block in blocks %} {% if block.start.date() == day.date() %} {% set _ = - day_blocks.append(block) %} {% endif %} {% endfor %} {# Group - blocks by hour for stacking #} {% set hour_groups = {} %} {% for - block in day_blocks %} {% set hour_key = block.hour %} {% if - hour_key not in hour_groups %} {% set _ = - hour_groups.update({hour_key: []}) %} {% endif %} {% set _ = - hour_groups[hour_key].append(block) %} {% endfor %} {# Render - blocks stacked within each hour #} {% for hour_key, hour_blocks - in hour_groups.items() %} {% for block in hour_blocks %} {% set - idx = loop.index0 %} {% set base_top_px = block.hour * 60 %} {% - set duration_hours = block.duration / 3600.0 %} {% set height_px - = duration_hours * 60 %} {% if height_px < 2 %}{% set height_px - = 2 %}{% endif %} {% set task_hash = block.task_path|hash if - block.task_path else 0 %} {% set base_color_hue = task_hash % - 360 %} {% set active_color = 'hsl(%d, 60%%, - 45%%)'|format(base_color_hue) %} {# Cascade effect: shift right - and down for overlapping blocks #} {# Each block shifts 8% right - and 4px down #} {% set offset_pct = idx * 8 %} {% set width_pct - = 100 - (idx * 8) - 2 %} {% set vertical_offset_px = idx * 4 %} - {% set top_px = base_top_px + vertical_offset_px %} + day_blocks.append(block) %} {% endif %} {% endfor %} {% set + hour_groups = {} %} {% for block in day_blocks %} {% set + hour_key = block.hour %} {% if hour_key not in hour_groups %} {% + set _ = hour_groups.update({hour_key: []}) %} {% endif %} {% set + _ = hour_groups[hour_key].append(block) %} {% endfor %} {% for + hour_key, hour_blocks in hour_groups.items() %} {% for block in + hour_blocks %} {% set idx = loop.index0 %} {% set base_top_px = + block.hour * 60 %} {% set duration_hours = block.duration / + 3600.0 %} {% set height_px = duration_hours * 60 %} {% if + height_px < 2 %}{% set height_px = 2 %}{% endif %} {% set + task_hash = block.task_path|hash if block.task_path else 0 %} {% + set base_color_hue = task_hash % 360 %} {% set active_color = + 'hsl(%d, 60%%, 45%%)'|format(base_color_hue) %} {% set + offset_pct = idx * 8 %} {% set width_pct = 100 - (idx * 8) - 2 + %} {% set vertical_offset_px = idx * 4 %} {% set top_px = + base_top_px + vertical_offset_px %}
{{ block.task_path }}
{{ (block.duration // 60)|int }}m
-
- {{ block.task_path }}
- Hour: {{ '%02d:00'|format(block.hour) }}
- Duration: {{ (block.duration // 60)|int }} minutes -
{% endfor %} {% endfor %} diff --git a/dmapp/dmweb/templates/switches_view.html b/dmapp/dmweb/templates/switches_view.html index 575dcfb..437c442 100644 --- a/dmapp/dmweb/templates/switches_view.html +++ b/dmapp/dmweb/templates/switches_view.html @@ -148,24 +148,29 @@ " > Today - Calendar + Calendar Switches All Time +