Add ability to move forward/back on week/month view and improve UI
This commit is contained in:
@@ -34,15 +34,36 @@ def dashboard():
|
|||||||
|
|
||||||
# Pagination for list view
|
# Pagination for list view
|
||||||
page = request.args.get('page', 1, type=int)
|
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
|
# Fetch paginated readings for the list view
|
||||||
paginated = fetch_readings_paginated(current_user.id, start_date, end_date, user_tz, page, PAGE_SIZE)
|
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)
|
# For calendar/graph/badges, fetch only current month + week readings (much smaller set)
|
||||||
now = datetime.now(user_tz)
|
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 = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||||
month_start_utc = month_start.astimezone(utc)
|
month_start_utc = month_start.astimezone(utc)
|
||||||
calendar_readings = fetch_readings_for_range(current_user.id, month_start_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 all readings with relative and localized timestamps
|
||||||
annotate_readings(paginated.items, user_tz)
|
annotate_readings(paginated.items, user_tz)
|
||||||
@@ -52,8 +73,8 @@ def dashboard():
|
|||||||
readings_by_day = build_readings_by_day(calendar_readings, user_tz)
|
readings_by_day = build_readings_by_day(calendar_readings, user_tz)
|
||||||
|
|
||||||
# Generate calendar views from the shared lookup
|
# Generate calendar views from the shared lookup
|
||||||
week_view = generate_weekly_calendar(readings_by_day, now, user_tz)
|
week_view = generate_weekly_calendar(readings_by_day, target_week_date, user_tz)
|
||||||
month_view = generate_monthly_calendar(readings_by_day, now, 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 (much faster than Python)
|
||||||
systolic_avg, diastolic_avg, heart_rate_avg = calculate_weekly_summary_sql(current_user.id)
|
systolic_avg, diastolic_avg, heart_rate_avg = calculate_weekly_summary_sql(current_user.id)
|
||||||
@@ -79,6 +100,10 @@ def dashboard():
|
|||||||
month=month_view,
|
month=month_view,
|
||||||
week=week_view,
|
week=week_view,
|
||||||
date=date,
|
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
|
**graph_data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -791,6 +791,10 @@ video {
|
|||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mt-0\.5 {
|
||||||
|
margin-top: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -860,6 +864,14 @@ video {
|
|||||||
height: 0.875rem;
|
height: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.min-h-\[140px\] {
|
||||||
|
min-height: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.min-h-\[120px\] {
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
.w-16 {
|
.w-16 {
|
||||||
width: 4rem;
|
width: 4rem;
|
||||||
}
|
}
|
||||||
@@ -1004,6 +1016,14 @@ video {
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gap-1 {
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-0\.5 {
|
||||||
|
gap: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
|
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
|
||||||
--tw-space-x-reverse: 0;
|
--tw-space-x-reverse: 0;
|
||||||
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
|
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
|
||||||
@@ -1034,10 +1054,26 @@ video {
|
|||||||
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
|
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.space-y-1\.5 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-y-reverse: 0;
|
||||||
|
margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse)));
|
||||||
|
margin-bottom: calc(0.375rem * var(--tw-space-y-reverse));
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-y-1 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-y-reverse: 0;
|
||||||
|
margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
|
||||||
|
margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
|
||||||
|
}
|
||||||
|
|
||||||
.overflow-hidden {
|
.overflow-hidden {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.whitespace-nowrap {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.rounded {
|
.rounded {
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
@@ -1131,6 +1167,11 @@ video {
|
|||||||
border-color: rgb(13 148 136 / var(--tw-border-opacity, 1));
|
border-color: rgb(13 148 136 / var(--tw-border-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-primary-400 {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(45 212 191 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.bg-blue-600 {
|
.bg-blue-600 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
|
background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
|
||||||
@@ -1225,6 +1266,11 @@ video {
|
|||||||
background-color: rgb(204 251 241 / var(--tw-bg-opacity, 1));
|
background-color: rgb(204 251 241 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-red-50 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.bg-gradient-to-r {
|
.bg-gradient-to-r {
|
||||||
background-image: linear-gradient(to right, var(--tw-gradient-stops));
|
background-image: linear-gradient(to right, var(--tw-gradient-stops));
|
||||||
}
|
}
|
||||||
@@ -1314,6 +1360,10 @@ video {
|
|||||||
padding: 2.5rem;
|
padding: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-1\.5 {
|
||||||
|
padding: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.px-2 {
|
.px-2 {
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
padding-right: 0.5rem;
|
padding-right: 0.5rem;
|
||||||
@@ -1374,6 +1424,21 @@ video {
|
|||||||
padding-bottom: 6rem;
|
padding-bottom: 6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.px-1 {
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
padding-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-1\.5 {
|
||||||
|
padding-left: 0.375rem;
|
||||||
|
padding-right: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-0\.5 {
|
||||||
|
padding-top: 0.125rem;
|
||||||
|
padding-bottom: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
.pl-1 {
|
.pl-1 {
|
||||||
padding-left: 0.25rem;
|
padding-left: 0.25rem;
|
||||||
}
|
}
|
||||||
@@ -1467,6 +1532,14 @@ video {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-\[10px\] {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-\[9px\] {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
.font-bold {
|
.font-bold {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
@@ -1495,6 +1568,10 @@ video {
|
|||||||
line-height: 1.625;
|
line-height: 1.625;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.leading-none {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.tracking-tight {
|
.tracking-tight {
|
||||||
letter-spacing: -0.025em;
|
letter-spacing: -0.025em;
|
||||||
}
|
}
|
||||||
@@ -1597,6 +1674,10 @@ video {
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.opacity-50 {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
.shadow {
|
.shadow {
|
||||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
||||||
@@ -1684,6 +1765,16 @@ video {
|
|||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:border-primary-400:hover {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(45 212 191 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:border-primary-300:hover {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(94 234 212 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:bg-blue-700:hover {
|
.hover\:bg-blue-700:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1));
|
background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1));
|
||||||
@@ -1739,6 +1830,11 @@ video {
|
|||||||
background-color: rgb(15 118 110 / var(--tw-bg-opacity, 1));
|
background-color: rgb(15 118 110 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:bg-primary-50:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(240 253 250 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:text-gray-200:hover {
|
.hover\:text-gray-200:hover {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(229 231 235 / var(--tw-text-opacity, 1));
|
color: rgb(229 231 235 / var(--tw-text-opacity, 1));
|
||||||
@@ -1890,6 +1986,15 @@ video {
|
|||||||
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
|
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
|
||||||
margin-bottom: calc(0px * var(--tw-space-y-reverse));
|
margin-bottom: calc(0px * var(--tw-space-y-reverse));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sm\:text-\[10px\] {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:text-xs {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
@@ -1963,6 +2068,10 @@ video {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
@media (min-width: 1280px) {
|
||||||
|
.xl\:mb-0 {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.xl\:block {
|
.xl\:block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -1970,4 +2079,18 @@ video {
|
|||||||
.xl\:hidden {
|
.xl\:hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xl\:flex-row {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1536px) {
|
||||||
|
.\32xl\:mt-0 {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\32xl\:flex-row {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="max-w-5xl mx-auto" x-data="{ activeView: 'list' }">
|
<div class="max-w-5xl mx-auto" x-data="{ activeView: '{{ active_view }}' }">
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<div class="flex border-b mb-4">
|
<div class="flex border-b mb-4">
|
||||||
<button @click="activeView = 'list'" :class="{'border-primary-600 text-primary-600': activeView === 'list'}"
|
<button @click="activeView = 'list'" :class="{'border-primary-600 text-primary-600': activeView === 'list'}"
|
||||||
@@ -176,37 +176,84 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Weekly View -->
|
<!-- Weekly View -->
|
||||||
<div x-show="activeView === 'weekly'" class="grid grid-cols-7 text-center">
|
<div x-show="activeView === 'weekly'">
|
||||||
{% for day in week %}
|
<!-- Weekly Navigation -->
|
||||||
<div class="border p-1 md:p-4 bg-gray-50">
|
<div class="flex justify-between items-center mb-4 px-2">
|
||||||
<div class="text-sm font-bold text-gray-500">{{ day.date }}</div>
|
<a href="{{ url_for('main.dashboard', activeView='weekly', week_offset=week_offset - 1) }}"
|
||||||
{% if day.readings %}
|
class="flex items-center text-primary-600 hover:text-primary-800 font-medium transition-colors">
|
||||||
{% for reading in day.readings %}
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24"
|
||||||
<a href="{{ url_for('reading.edit_reading', reading_id=reading.id) }}"
|
stroke="currentColor" stroke-width="2">
|
||||||
class="block mt-2 p-0 md:p-2 bg-green-100 rounded-xl shadow hover:bg-green-200 transition">
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
|
||||||
<p class="text-xs font-medium text-green-800">
|
</svg>
|
||||||
{{ reading.systolic }}/{{ reading.diastolic }} mmHg
|
Previous Week
|
||||||
</p>
|
|
||||||
<p class="text-xs text-gray-600 mt-1">{{ reading.heart_rate }} bpm</p>
|
|
||||||
<!-- Timestamp -->
|
|
||||||
<div class="text-xs text-gray-500 mt-1">
|
|
||||||
{{ reading.local_timestamp.strftime('%I:%M %p') }}
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
<span class="text-gray-600 font-semibold {% if week_offset == 0 %}text-primary-600{% endif %}">
|
||||||
<div class="h-8"></div> <!-- Placeholder for spacing -->
|
{% if week_offset == 0 %}This Week{% elif week_offset == -1 %}Last Week{% elif week_offset == 1
|
||||||
{% endif %}
|
%}Next Week{% else %}{{ week_offset|abs }} weeks {% if week_offset < 0 %}ago{% else %}from now{%
|
||||||
|
endif %}{% endif %} </span>
|
||||||
|
|
||||||
|
<a href="{{ url_for('main.dashboard', activeView='weekly', week_offset=week_offset + 1) }}"
|
||||||
|
class="flex items-center text-primary-600 hover:text-primary-800 font-medium transition-colors {% if week_offset >= 0 %}opacity-50 pointer-events-none{% endif %}">
|
||||||
|
Next Week
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-1" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor" stroke-width="2">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-7 text-center">
|
||||||
|
{% for day in week %}
|
||||||
|
<div class="border p-2 bg-gray-50 flex flex-col min-h-[140px]">
|
||||||
|
<div class="text-sm font-bold text-gray-500 mb-2">{{ day.date }}</div>
|
||||||
|
{% if day.readings %}
|
||||||
|
<div class="space-y-1.5 flex-grow">
|
||||||
|
{% for reading in day.readings %}
|
||||||
|
<a href="{{ url_for('reading.edit_reading', reading_id=reading.id) }}"
|
||||||
|
class="flex flex-col 2xl:flex-row justify-between items-center px-1.5 py-1 bg-white border border-gray-200 rounded shadow-sm hover:border-primary-400 hover:bg-primary-50 transition-colors">
|
||||||
|
<span class="text-xs font-bold text-gray-800">{{ reading.systolic }}/{{ reading.diastolic
|
||||||
|
}}</span>
|
||||||
|
<div class="flex items-center gap-1 2xl:mt-0 mt-0.5">
|
||||||
|
<span class="text-[10px] font-medium bg-red-50 text-red-600 px-1 rounded"
|
||||||
|
title="Heart Rate">{{ reading.heart_rate }}</span>
|
||||||
|
<span class="text-[10px] text-gray-400 font-medium whitespace-nowrap">{{
|
||||||
|
reading.local_timestamp.strftime('%H:%M') }}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="flex-grow"></div> <!-- Spacer -->
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Monthly View -->
|
<!-- Monthly View -->
|
||||||
<div class="flex flex-col px-2 py-2 -mb-px" x-show="activeView === 'monthly'">
|
<div class="flex flex-col px-2 py-2 -mb-px" x-show="activeView === 'monthly'">
|
||||||
{% set current_date = date.today().replace(day=1) %}
|
<!-- Monthly Navigation -->
|
||||||
<!-- Month Name -->
|
<div class="flex justify-between items-center mb-4 px-2 mt-2">
|
||||||
<div class="text-center">
|
<a href="{{ url_for('main.dashboard', activeView='monthly', month_offset=month_offset - 1) }}"
|
||||||
<h2 class="text-xl font-bold text-gray-800">{{ current_date.strftime('%B %Y') }}</h2>
|
class="flex items-center text-primary-600 hover:text-primary-800 font-medium transition-colors">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor" stroke-width="2">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
Previous Month
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<h2 class="text-xl font-bold text-gray-800">{{ target_month_date.strftime('%B %Y') }}</h2>
|
||||||
|
|
||||||
|
<a href="{{ url_for('main.dashboard', activeView='monthly', month_offset=month_offset + 1) }}"
|
||||||
|
class="flex items-center text-primary-600 hover:text-primary-800 font-medium transition-colors {% if month_offset >= 0 %}opacity-50 pointer-events-none{% endif %}">
|
||||||
|
Next Month
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-1" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor" stroke-width="2">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-7 pl-2 pr-2">
|
<div class="grid grid-cols-7 pl-2 pr-2">
|
||||||
@@ -245,23 +292,27 @@
|
|||||||
|
|
||||||
{% for day in month %}
|
{% for day in month %}
|
||||||
<div
|
<div
|
||||||
class="{% if day.is_today %}rounded-md border-4 border-green-50{% endif %} border flex flex-col h-36 sm:h-40 md:h-30 lg:h-30 mx-auto mx-auto overflow-hidden w-full pt-2 pl-1 cursor-pointer {% if day.is_in_current_month %}bg-gray-100{% endif %}">
|
class="{% if day.is_today %}border-2 border-primary-400 bg-primary-50{% else %}border bg-white{% endif %} flex flex-col min-h-[120px] p-1.5 transition-colors {% if not day.is_in_current_month %}bg-gray-50 opacity-50{% endif %}">
|
||||||
<div class="top h-5 w-full">
|
<div class="text-right w-full mb-1">
|
||||||
<span class="text-gray-500 font-semibold">{{ day.day }}</span>
|
<span
|
||||||
|
class="text-xs font-semibold {% if day.is_today %}text-primary-600{% else %}text-gray-500{% endif %}">{{
|
||||||
|
day.day }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-1 flex-grow">
|
||||||
|
{% for reading in day.readings %}
|
||||||
|
<a href="{{ url_for('reading.edit_reading', reading_id=reading.id) }}"
|
||||||
|
class="flex flex-col xl:flex-row justify-between items-center px-1 py-1 bg-white border border-gray-100 rounded shadow-sm hover:border-primary-300 hover:bg-primary-50 transition-colors">
|
||||||
|
<span class="text-[10px] sm:text-xs font-bold text-gray-800 leading-none mb-0.5 xl:mb-0">{{
|
||||||
|
reading.systolic }}/{{ reading.diastolic }}</span>
|
||||||
|
<div class="flex items-center gap-0.5">
|
||||||
|
<span
|
||||||
|
class="text-[9px] sm:text-[10px] font-medium bg-red-50 text-red-600 px-1 rounded leading-none py-0.5"
|
||||||
|
title="Heart Rate">{{ reading.heart_rate }}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% for reading in day.readings %}
|
|
||||||
<a href="{{ url_for('reading.edit_reading', reading_id=reading.id) }}"
|
|
||||||
class="block mt-2 p-0 md:p-2 bg-green-100 rounded-xl shadow hover:bg-green-200 transition">
|
|
||||||
<p class="text-xs font-medium text-green-800">
|
|
||||||
{{ reading.systolic }}/{{ reading.diastolic }} mmHg
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-gray-600 mt-1">{{ reading.heart_rate }} bpm</p>
|
|
||||||
<!-- Timestamp -->
|
|
||||||
<div class="text-xs text-gray-500 mt-1">
|
|
||||||
{{ reading.local_timestamp.strftime('%I:%M %p') }}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
@@ -280,12 +331,14 @@
|
|||||||
|
|
||||||
<!-- Y-axis Labels (Blood Pressure Values) -->
|
<!-- Y-axis Labels (Blood Pressure Values) -->
|
||||||
{% for value in range(50, 201, 50) %}
|
{% for value in range(50, 201, 50) %}
|
||||||
<text x="40" y="{{ 200 - (value / 200 * 180) }}" font-size="10" text-anchor="end">{{ value }}</text>
|
<text x="40" y="{{ 200 - (value / 200 * 180) }}" font-size="10" text-anchor="end">{{ value
|
||||||
|
}}</text>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<!-- X-axis Labels (Timestamps) -->
|
<!-- X-axis Labels (Timestamps) -->
|
||||||
{% for i in range(timestamps|length) %}
|
{% for i in range(timestamps|length) %}
|
||||||
<text x="{{ 50 + i * 50 }}" y="215" font-size="10" text-anchor="middle">{{ timestamps[i] }}</text>
|
<text x="{{ 50 + i * 50 }}" y="215" font-size="10" text-anchor="middle">{{ timestamps[i]
|
||||||
|
}}</text>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<!-- Graph Lines -->
|
<!-- Graph Lines -->
|
||||||
@@ -314,12 +367,14 @@
|
|||||||
|
|
||||||
<!-- Y-axis Labels (Heart Rate Values) -->
|
<!-- Y-axis Labels (Heart Rate Values) -->
|
||||||
{% for value in range(50, 201, 50) %}
|
{% for value in range(50, 201, 50) %}
|
||||||
<text x="40" y="{{ 200 - (value / 200 * 180) }}" font-size="10" text-anchor="end">{{ value }}</text>
|
<text x="40" y="{{ 200 - (value / 200 * 180) }}" font-size="10" text-anchor="end">{{ value
|
||||||
|
}}</text>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<!-- X-axis Labels (Timestamps) -->
|
<!-- X-axis Labels (Timestamps) -->
|
||||||
{% for i in range(timestamps|length) %}
|
{% for i in range(timestamps|length) %}
|
||||||
<text x="{{ 50 + i * 50 }}" y="215" font-size="10" text-anchor="middle">{{ timestamps[i] }}</text>
|
<text x="{{ 50 + i * 50 }}" y="215" font-size="10" text-anchor="middle">{{ timestamps[i]
|
||||||
|
}}</text>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<!-- Heart Rate Line -->
|
<!-- Heart Rate Line -->
|
||||||
@@ -327,7 +382,8 @@
|
|||||||
points="{% for i in range(timestamps|length) %}{{ 50 + i * 50 }},{{ 200 - (heart_rate[i] / 200 * 180) }} {% endfor %}" />
|
points="{% for i in range(timestamps|length) %}{{ 50 + i * 50 }},{{ 200 - (heart_rate[i] / 200 * 180) }} {% endfor %}" />
|
||||||
|
|
||||||
<!-- Axis Labels -->
|
<!-- Axis Labels -->
|
||||||
<text x="25" y="110" font-size="12" transform="rotate(-90, 25, 110)" text-anchor="middle">Heart Rate
|
<text x="25" y="110" font-size="12" transform="rotate(-90, 25, 110)" text-anchor="middle">Heart
|
||||||
|
Rate
|
||||||
(bpm)</text>
|
(bpm)</text>
|
||||||
<text x="300" y="240" font-size="12" text-anchor="middle">Date</text>
|
<text x="300" y="240" font-size="12" text-anchor="middle">Date</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
Reference in New Issue
Block a user