diff --git a/CHANGELOG.md b/CHANGELOG.md index 75e51c7b..bfa131b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- **Dashboard timer widget** — Pause and Stop buttons while a timer is running (Pause saves the segment so you can resume later). When no timer is active, a prominent "Resume (project name)" button restarts tracking with the same project/task/notes as your last entry. Quick time adjustment buttons (−15 / −5 / +5 / +15 minutes) let you correct the current session without leaving the dashboard. New route `POST /timer/adjust` for start-time adjustment. + ### Changed - **Log Time Manually page** — Redesigned for a more professional layout: form grouped into sections (Project & task, Date & time, Details) with clear headings and icons; main card uses rounded-xl and shadow-lg; unified label and helper text styling; primary "Log Time" and secondary "Clear" buttons aligned with dashboard button styles; duplicate-entry banner uses rounded-xl. diff --git a/app/routes/timer.py b/app/routes/timer.py index c21db34c..872cdce5 100644 --- a/app/routes/timer.py +++ b/app/routes/timer.py @@ -538,6 +538,49 @@ def stop_timer(): return redirect(url_for("main.dashboard")) +@timer_bp.route("/timer/adjust", methods=["POST"]) +@login_required +def adjust_timer(): + """Adjust the active timer's start time by delta_minutes (positive = add time, negative = subtract).""" + active_timer = current_user.active_timer + if not active_timer: + flash(_("No active timer to adjust"), "error") + return redirect(url_for("main.dashboard")) + + try: + delta_minutes = int(request.form.get("delta_minutes", 0)) + except (TypeError, ValueError): + flash(_("Invalid adjustment value"), "error") + return redirect(url_for("main.dashboard")) + + if delta_minutes == 0: + return redirect(url_for("main.dashboard")) + + # Clamp to avoid extreme shifts (e.g. ±4 hours) + delta_minutes = max(-240, min(240, delta_minutes)) + from app.models.time_entry import local_now + + new_start = active_timer.start_time - timedelta(minutes=delta_minutes) + # Do not set start_time in the future + now_local = local_now() + if new_start > now_local: + new_start = now_local + active_timer.start_time = new_start + active_timer.updated_at = now_local + db.session.commit() + + try: + from app.utils.cache import get_cache + cache = get_cache() + cache.delete(f"dashboard:{current_user.id}") + except Exception: + pass + + if request.headers.get("X-Requested-With") == "XMLHttpRequest": + return jsonify({"success": True, "start_time": active_timer.start_time.isoformat()}) + return redirect(url_for("main.dashboard")) + + @timer_bp.route("/timer/status") @login_required def timer_status(): diff --git a/app/templates/main/dashboard.html b/app/templates/main/dashboard.html index cd1a8a6c..75da4ed7 100644 --- a/app/templates/main/dashboard.html +++ b/app/templates/main/dashboard.html @@ -128,18 +128,61 @@

{{ _('Elapsed') }}: {{ active_timer.duration_formatted }}

+
+ {{ _('Adjust time') }}: +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ +
+
+ + +
+
+ + +
-
- - -
{% else %}

{{ _('No active timer.') }}

-

{{ _('Click "Start Timer" to begin tracking your time.') }}

+ {% if recent_entries %} +

{{ _('Resume your last session or start a new timer.') }}

+
+ + {{ _('Resume') }}{% if recent_entries[0].project %} ({{ recent_entries[0].project.name }}){% elif recent_entries[0].client %} ({{ recent_entries[0].client.name }}){% endif %} + + +
+ {% else %} +

{{ _('Click "Start Timer" above to begin tracking your time.') }}

+ {% endif %}
{% endif %} diff --git a/app/templates/main/help.html b/app/templates/main/help.html index a9423751..0d9ed62d 100644 --- a/app/templates/main/help.html +++ b/app/templates/main/help.html @@ -133,6 +133,9 @@

{{ _('Timer Features') }}