diff --git a/app/routes/main.py b/app/routes/main.py index 82d4af8..59262cc 100644 --- a/app/routes/main.py +++ b/app/routes/main.py @@ -24,89 +24,118 @@ def health(): @main.route('/dashboard', methods=['GET', 'POST']) @login_required def dashboard(): - """Render the dashboard with readings, stats, and calendar views.""" + """Render the dashboard shell and default list view.""" user_tz = timezone(current_user.profile.timezone or 'UTC') - # Get date range for readings + # Get date range for filters first_reading, last_reading = get_reading_date_range(current_user.id, user_tz) - start_date = request.form.get('start_date') or (first_reading and first_reading.strftime('%Y-%m-%d')) - end_date = request.form.get('end_date') or (last_reading and last_reading.strftime('%Y-%m-%d')) + start_date = request.form.get('start_date') or request.args.get('start_date') or (first_reading and first_reading.strftime('%Y-%m-%d')) + end_date = request.form.get('end_date') or request.args.get('end_date') or (last_reading and last_reading.strftime('%Y-%m-%d')) - # Pagination for list view - page = request.args.get('page', 1, type=int) - - # Weekly View Navigation Offset - week_offset = request.args.get('week_offset', 0, type=int) - - # Monthly View Navigation Offset - month_offset = request.args.get('month_offset', 0, type=int) - - # Fetch paginated readings for the list view - paginated = fetch_readings_paginated(current_user.id, start_date, end_date, user_tz, page, PAGE_SIZE) - - # For calendar/graph/badges, fetch only current month + week readings (much smaller set) - now = datetime.now(user_tz) - - # Calculate target week date for weekly view - target_week_date = now + timedelta(weeks=week_offset) - # Ensure we fetch enough data back to cover the week_offset - month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) - month_start_utc = month_start.astimezone(utc) - target_week_start = target_week_date - timedelta(days=target_week_date.weekday()) - target_week_start_utc = target_week_start.replace(hour=0, minute=0, second=0, microsecond=0).astimezone(utc) - - # Calculate target month date for monthly view - target_month_year = now.year + (now.month + month_offset - 1) // 12 - target_month_month = (now.month + month_offset - 1) % 12 + 1 - target_month_date = now.replace(year=target_month_year, month=target_month_month, day=1, hour=0, minute=0, second=0, microsecond=0) - target_month_start_utc = target_month_date.astimezone(utc) - - # Fetch from the earliest of the current month OR the requested target week/month - fetch_start_utc = min(month_start_utc, target_week_start_utc, target_month_start_utc) - calendar_readings = fetch_readings_for_range(current_user.id, fetch_start_utc) - - # Annotate all readings with relative and localized timestamps - annotate_readings(paginated.items, user_tz) - annotate_readings(calendar_readings, user_tz) - - # Build shared lookup for calendar views (single pass) - readings_by_day = build_readings_by_day(calendar_readings, user_tz) - - # Generate calendar views from the shared lookup - week_view = generate_weekly_calendar(readings_by_day, target_week_date, user_tz) - month_view = generate_monthly_calendar(readings_by_day, target_month_date, user_tz) - - # Calculate weekly averages via SQL (much faster than Python) + # Calculate weekly averages via SQL systolic_avg, diastolic_avg, heart_rate_avg = calculate_weekly_summary_sql(current_user.id) - # Badges from the paginated readings (or full set if needed) - badges = calculate_progress_badges(paginated.items) - - # Prepare graph data from calendar readings (current month) - graph_data = prepare_graph_data(calendar_readings) + # Badges need paginated or all readings. We'll fetch all readings for badges + # Note: To avoid huge queries, we might want a specific badge query in the future. + # For now, let's fetch current month readings + previous as a rough approximation or keep behavior. + # A generic query for badges: + all_readings = Reading.query.filter_by(user_id=current_user.id).order_by(Reading.timestamp.desc()).all() + badges = calculate_progress_badges(all_readings) + + # We will default to showing the list view on initial load + page = request.args.get('page', 1, type=int) + paginated = fetch_readings_paginated(current_user.id, start_date, end_date, user_tz, page, PAGE_SIZE) + annotate_readings(paginated.items, user_tz) return render_template( 'dashboard.html', - readings=paginated.items, - pagination=paginated, profile=current_user.profile, badges=badges, systolic_avg=systolic_avg, diastolic_avg=diastolic_avg, heart_rate_avg=heart_rate_avg, - delete_form=DeleteForm(), start_date=start_date, end_date=end_date, - month=month_view, - week=week_view, - date=date, - target_month_date=target_month_date, - week_offset=week_offset, - month_offset=month_offset, - active_view=request.args.get('activeView', 'list'), - **graph_data + delete_form=DeleteForm(), + active_view='list', + # default view context + readings=paginated.items, + pagination=paginated ) +@main.route('/dashboard/list', methods=['GET']) +@login_required +def dashboard_list(): + user_tz = timezone(current_user.profile.timezone or 'UTC') + first_reading, last_reading = get_reading_date_range(current_user.id, user_tz) + start_date = request.args.get('start_date') or (first_reading and first_reading.strftime('%Y-%m-%d')) + end_date = request.args.get('end_date') or (last_reading and last_reading.strftime('%Y-%m-%d')) + page = request.args.get('page', 1, type=int) + + paginated = fetch_readings_paginated(current_user.id, start_date, end_date, user_tz, page, PAGE_SIZE) + annotate_readings(paginated.items, user_tz) + + return render_template('partials/dashboard_list.html', readings=paginated.items, pagination=paginated) + +@main.route('/dashboard/weekly', methods=['GET']) +@login_required +def dashboard_weekly(): + user_tz = timezone(current_user.profile.timezone or 'UTC') + week_offset = request.args.get('week_offset', 0, type=int) + now = datetime.now(user_tz) + + target_week_date = now + timedelta(weeks=week_offset) + target_week_start = target_week_date - timedelta(days=target_week_date.weekday()) + target_week_start_utc = target_week_start.replace(hour=0, minute=0, second=0, microsecond=0).astimezone(utc) + target_week_end_utc = (target_week_start + timedelta(days=7)).replace(hour=0, minute=0, second=0, microsecond=0).astimezone(utc) + + calendar_readings = fetch_readings_for_range(current_user.id, target_week_start_utc, target_week_end_utc) + annotate_readings(calendar_readings, user_tz) + readings_by_day = build_readings_by_day(calendar_readings, user_tz) + week_view = generate_weekly_calendar(readings_by_day, target_week_date, user_tz) + + return render_template('partials/dashboard_weekly.html', week=week_view, week_offset=week_offset) + +@main.route('/dashboard/monthly', methods=['GET']) +@login_required +def dashboard_monthly(): + user_tz = timezone(current_user.profile.timezone or 'UTC') + month_offset = request.args.get('month_offset', 0, type=int) + now = datetime.now(user_tz) + + target_month_year = now.year + (now.month + month_offset - 1) // 12 + target_month_month = (now.month + month_offset - 1) % 12 + 1 + target_month_date = now.replace(year=target_month_year, month=target_month_month, day=1, hour=0, minute=0, second=0, microsecond=0) + + first_day = target_month_date + start_date = first_day - timedelta(days=(first_day.weekday() + 1) % 7) + end_date = start_date + timedelta(days=42) + + start_utc = start_date.replace(hour=0, minute=0, second=0, microsecond=0).astimezone(utc) + end_utc = end_date.replace(hour=0, minute=0, second=0, microsecond=0).astimezone(utc) + + calendar_readings = fetch_readings_for_range(current_user.id, start_utc, end_utc) + annotate_readings(calendar_readings, user_tz) + readings_by_day = build_readings_by_day(calendar_readings, user_tz) + month_view = generate_monthly_calendar(readings_by_day, target_month_date, user_tz) + + return render_template('partials/dashboard_monthly.html', month=month_view, target_month_date=target_month_date, month_offset=month_offset) + +@main.route('/dashboard/graph', methods=['GET']) +@login_required +def dashboard_graph(): + user_tz = timezone(current_user.profile.timezone or 'UTC') + now = datetime.now(user_tz) + month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + month_start_utc = month_start.astimezone(utc) + + calendar_readings = fetch_readings_for_range(current_user.id, month_start_utc) + annotate_readings(calendar_readings, user_tz) + + graph_data = prepare_graph_data(calendar_readings) + + return render_template('partials/dashboard_graph.html', **graph_data) + def get_reading_date_range(user_id, user_tz): """Fetch the earliest and latest reading timestamps for a user.""" result = db.session.query( diff --git a/app/templates/_layout.html b/app/templates/_layout.html index d6907e7..28d312e 100644 --- a/app/templates/_layout.html +++ b/app/templates/_layout.html @@ -9,6 +9,7 @@ + diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 95054dd..e1b3f6b 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -87,310 +87,25 @@
- - - -
- -
- {% for reading in readings %} - - - -
-
- - - - - {{ reading.relative_timestamp }} - -
-
- {{ reading.systolic }}/{{ reading.diastolic }} - mmHg -
-
- - -
-
- HR -
- {{ reading.heart_rate }} - bpm -
-
-
- - - -
-
-
- {% else %} -
- No readings found. -
- {% endfor %} + +
+ {% include 'partials/dashboard_list.html' %}
- - - {% if pagination.pages > 1 %} -
- {% if pagination.has_prev %} - « Prev - {% endif %} - - {% for page_num in pagination.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %} - {% if page_num %} - - {{ page_num }} - - {% else %} - - {% endif %} - {% endfor %} - - {% if pagination.has_next %} - Next » - {% endif %} -
- {% endif %} - - -
- -
- - - - - Previous Week - - - - {% if week_offset == 0 %}This Week{% elif week_offset == -1 %}Last Week{% elif week_offset == 1 - %}Next Week{% else %}{{ week_offset|abs }} weeks {% if week_offset < 0 %}ago{% else %}from now{% - endif %}{% endif %} - - - Next Week - - - - -
- -
- {% for day in week %} -
-
{{ day.date }}
- {% if day.readings %} - - {% else %} -
- {% endif %} -
- {% endfor %} -
-
- - -
- -
- - - - - Previous Month - - -

{{ target_month_date.strftime('%B %Y') }}

- - - Next Month - - - - -
- -
- -
- - Sun -
-
- - Mon -
-
- - Tue -
-
- - Wed -
-
- - Thu -
-
- - Fri -
-
- - Sat -
-
- -
- - {% for day in month %} -
-
- {{ - day.day }} -
- - -
- {% endfor %} - -
-
- - -
- -
-

Blood Pressure (mmHg)

- - - - - - - {% for value in range(50, 201, 50) %} - {{ value - }} - {% endfor %} - - - {% for i in range(timestamps|length) %} - {{ timestamps[i] - }} - {% endfor %} - - - - - - - - - - Blood - Pressure (mmHg) - Date - -
- - -
-

Heart Rate (bpm)

- - - - - - - {% for value in range(50, 201, 50) %} - {{ value - }} - {% endfor %} - - - {% for i in range(timestamps|length) %} - {{ timestamps[i] - }} - {% endfor %} - - - - - - Heart - Rate - (bpm) - Date - -
-
- -
diff --git a/app/templates/partials/dashboard_graph.html b/app/templates/partials/dashboard_graph.html new file mode 100644 index 0000000..0502dd9 --- /dev/null +++ b/app/templates/partials/dashboard_graph.html @@ -0,0 +1,64 @@ +
+ +
+

Blood Pressure (mmHg)

+ + + + + + + {% for value in range(50, 201, 50) %} + {{ value }} + {% endfor %} + + + {% for i in range(timestamps|length) %} + {{ timestamps[i] }} + {% endfor %} + + + + + + + + + + Blood Pressure + (mmHg) + Date + +
+ + +
+

Heart Rate (bpm)

+ + + + + + + {% for value in range(50, 201, 50) %} + {{ value }} + {% endfor %} + + + {% for i in range(timestamps|length) %} + {{ timestamps[i] }} + {% endfor %} + + + + + + Heart Rate + (bpm) + Date + +
+
\ No newline at end of file diff --git a/app/templates/partials/dashboard_list.html b/app/templates/partials/dashboard_list.html new file mode 100644 index 0000000..c03149c --- /dev/null +++ b/app/templates/partials/dashboard_list.html @@ -0,0 +1,73 @@ +
+ {% for reading in readings %} + + + +
+
+ + + + + {{ reading.relative_timestamp }} + +
+
+ {{ reading.systolic }}/{{ reading.diastolic }} + mmHg +
+
+ + +
+
+ HR +
+ {{ reading.heart_rate }} + bpm +
+
+
+ + + +
+
+
+ {% else %} +
+ No readings found. +
+ {% endfor %} +
+ + +{% if pagination.pages > 1 %} +
+ {% if pagination.has_prev %} + + {% endif %} + + {% for page_num in pagination.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %} + {% if page_num %} + + {% else %} + + {% endif %} + {% endfor %} + + {% if pagination.has_next %} + + {% endif %} +
+{% endif %} \ No newline at end of file diff --git a/app/templates/partials/dashboard_monthly.html b/app/templates/partials/dashboard_monthly.html new file mode 100644 index 0000000..e6c2b71 --- /dev/null +++ b/app/templates/partials/dashboard_monthly.html @@ -0,0 +1,88 @@ +
+ +
+ + +

{{ target_month_date.strftime('%B %Y') }}

+ + +
+ +
+ +
+ + Sun +
+
+ + Mon +
+
+ + Tue +
+
+ + Wed +
+
+ + Thu +
+
+ + Fri +
+
+ + Sat +
+
+ +
+ + {% for day in month %} +
+
+ {{ + day.day }} +
+ + +
+ {% endfor %} + +
+
\ No newline at end of file diff --git a/app/templates/partials/dashboard_weekly.html b/app/templates/partials/dashboard_weekly.html new file mode 100644 index 0000000..62ba09c --- /dev/null +++ b/app/templates/partials/dashboard_weekly.html @@ -0,0 +1,56 @@ +
+ +
+ + + + {% if week_offset == 0 %}This Week{% elif week_offset == -1 %}Last Week{% elif week_offset == 1 %}Next + Week{% else %}{{ week_offset|abs }} weeks {% if week_offset < 0 %}ago{% else %}from now{% endif %}{% endif + %} + + +
+ +
+ {% for day in week %} +
+
{{ day.date }}
+ {% if day.readings %} + + {% else %} +
+ {% endif %} +
+ {% endfor %} +
+
\ No newline at end of file