Redesign BP tracker with lightweight views and unobtrusive save confirmations
This commit is contained in:
@@ -1,346 +0,0 @@
|
||||
<style>
|
||||
.svg-tooltip-group .svg-tooltip-content {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.svg-tooltip-group:hover .svg-tooltip-content {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
<div class="space-y-8">
|
||||
<!-- Graph Date Filter -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 mb-2">
|
||||
<form hx-get="{{ url_for('main.dashboard_graph') }}" hx-target="#dashboard-content"
|
||||
class="flex flex-col sm:flex-row gap-4">
|
||||
<!-- Start Date -->
|
||||
<div class="flex-1">
|
||||
<label for="start_date" class="block text-sm font-medium text-gray-700 mb-1">Start Date</label>
|
||||
<input type="date" name="start_date" id="start_date" value="{{ start_date or '' }}"
|
||||
class="w-full p-2.5 border border-gray-300 rounded-xl shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-500 text-gray-700">
|
||||
</div>
|
||||
|
||||
<!-- End Date -->
|
||||
<div class="flex-1">
|
||||
<label for="end_date" class="block text-sm font-medium text-gray-700 mb-1">End Date</label>
|
||||
<input type="date" name="end_date" id="end_date" value="{{ end_date or '' }}"
|
||||
class="w-full p-2.5 border border-gray-300 rounded-xl shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-500 text-gray-700">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- Blood Pressure Graph Card -->
|
||||
<div class="bg-white rounded-2xl shadow-lg border border-gray-100 p-6 transition-all hover:shadow-xl">
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-gray-800">Blood Pressure Trends</h3>
|
||||
<p class="text-sm text-gray-500 mt-1">Systolic vs Diastolic over time (mmHg)</p>
|
||||
</div>
|
||||
<div class="flex space-x-6 text-sm mt-4 md:mt-0 bg-gray-50 px-4 py-2 rounded-full">
|
||||
<div class="flex items-center"><span
|
||||
class="w-3 h-3 rounded-full bg-blue-500 mr-2 shadow-sm"></span><span
|
||||
class="font-medium text-gray-700">Systolic</span></div>
|
||||
<div class="flex items-center"><span
|
||||
class="w-3 h-3 rounded-full bg-pink-500 mr-2 shadow-sm"></span><span
|
||||
class="font-medium text-gray-700">Diastolic</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full overflow-x-auto pb-4">
|
||||
<svg viewBox="0 0 800 350" class="w-full min-w-[600px] h-auto drop-shadow-sm font-sans"
|
||||
style="max-height: 350px;">
|
||||
<defs>
|
||||
<linearGradient id="sysGrad" x1="0" x2="0" y1="0" y2="1">
|
||||
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.3" />
|
||||
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0.0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="diaGrad" x1="0" x2="0" y1="0" y2="1">
|
||||
<stop offset="0%" stop-color="#ec4899" stop-opacity="0.3" />
|
||||
<stop offset="100%" stop-color="#ec4899" stop-opacity="0.0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
{% if timestamps %}
|
||||
{% set n = timestamps|length %}
|
||||
{% set spacing = 700 / (n - 1) if n > 1 else 0 %}
|
||||
|
||||
<!-- Horizontal Grid Lines & Y-Axis Labels -->
|
||||
{% for value in range(50, 201, 50) %}
|
||||
{% set y = 280 - (value / 200 * 250) %}
|
||||
<line x1="50" y1="{{ y }}" x2="750" y2="{{ y }}" stroke="#e5e7eb" stroke-width="1"
|
||||
stroke-dasharray="4 4" />
|
||||
<text x="40" y="{{ y + 4 }}" font-size="12" fill="#9ca3af" text-anchor="end" font-weight="500">{{ value
|
||||
}}</text>
|
||||
{% endfor %}
|
||||
|
||||
<!-- X-Axis Base Line -->
|
||||
<line x1="50" y1="280" x2="750" y2="280" stroke="#d1d5db" stroke-width="1" />
|
||||
|
||||
<!-- X-Axis Labels -->
|
||||
{% set ns = namespace(last_x=-100) %}
|
||||
{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
<!-- Optimization: only draw label if it has enough horizontal space from the last drawn label -->
|
||||
{% set is_last = (i == n - 1) %}
|
||||
{% if x - ns.last_x > 45 or is_last %}
|
||||
<!-- If it's the last label but too close to the previous, maybe we draw it anyway but it will overlap. We prevent overlap by aggressively spacing. -->
|
||||
{% if not is_last or x - ns.last_x > 40 or ns.last_x == -100 %}
|
||||
<text x="{{ x }}" y="310" font-size="11" fill="#6b7280" text-anchor="middle" font-weight="500"
|
||||
transform="rotate(-25 {{ x }} 310)">{{
|
||||
timestamps[i] }}</text>
|
||||
{% set ns.last_x = x %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<!-- Systolic Filled Area -->
|
||||
<polygon fill="url(#sysGrad)" points="{% if n>1 %}50{% else %}400{% endif %},280
|
||||
{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
{% set y = 280 - (systolic[i] / 200 * 250) %}
|
||||
{{ x }},{{ y }}
|
||||
{% endfor %}
|
||||
{% if n>1 %}{{ 50 + (n - 1) * spacing }}{% else %}400{% endif %},280" />
|
||||
|
||||
<!-- Systolic Line -->
|
||||
<polyline fill="none" stroke="#3b82f6" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"
|
||||
points="{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
{% set y = 280 - (systolic[i] / 200 * 250) %}
|
||||
{{ x }},{{ y }}
|
||||
{% endfor %}" />
|
||||
|
||||
<!-- Systolic Points -->
|
||||
{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
{% set y = 280 - (systolic[i] / 200 * 250) %}
|
||||
<circle cx="{{ x }}" cy="{{ y }}" r="5" fill="#ffffff" stroke="#3b82f6" stroke-width="2.5"></circle>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Diastolic Filled Area -->
|
||||
<polygon fill="url(#diaGrad)" points="{% if n>1 %}50{% else %}400{% endif %},280
|
||||
{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
{% set y = 280 - (diastolic[i] / 200 * 250) %}
|
||||
{{ x }},{{ y }}
|
||||
{% endfor %}
|
||||
{% if n>1 %}{{ 50 + (n - 1) * spacing }}{% else %}400{% endif %},280" />
|
||||
|
||||
<!-- Diastolic Line -->
|
||||
<polyline fill="none" stroke="#ec4899" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"
|
||||
points="{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
{% set y = 280 - (diastolic[i] / 200 * 250) %}
|
||||
{{ x }},{{ y }}
|
||||
{% endfor %}" />
|
||||
|
||||
<!-- Diastolic Points -->
|
||||
{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
{% set y = 280 - (diastolic[i] / 200 * 250) %}
|
||||
<circle cx="{{ x }}" cy="{{ y }}" r="5" fill="#ffffff" stroke="#ec4899" stroke-width="2.5"></circle>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Tooltips (Rendered last to stay on top) -->
|
||||
{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
|
||||
{% set ySys = 280 - (systolic[i] / 200 * 250) %}
|
||||
<g class="svg-tooltip-group" style="cursor: pointer;">
|
||||
<circle cx="{{ x }}" cy="{{ ySys }}" r="15" fill="transparent" />
|
||||
<circle cx="{{ x }}" cy="{{ ySys }}" r="7" fill="#ffffff" stroke="#3b82f6" stroke-width="2.5"
|
||||
class="svg-tooltip-content" />
|
||||
<g class="svg-tooltip-content">
|
||||
<rect x="{{ x - 64 }}" y="{{ ySys - 54 }}" width="128" height="40" fill="#1f2937" rx="6" />
|
||||
<polygon points="{{ x - 6 }},{{ ySys - 14 }} {{ x + 6 }},{{ ySys - 14 }} {{ x }},{{ ySys - 6 }}"
|
||||
fill="#1f2937" />
|
||||
<text x="{{ x }}" y="{{ ySys - 36 }}" font-size="11" fill="#f3f4f6" text-anchor="middle"
|
||||
font-weight="600" class="font-sans">{{ timestamps[i] }}</text>
|
||||
<text x="{{ x }}" y="{{ ySys - 22 }}" font-size="11" fill="#93c5fd" text-anchor="middle"
|
||||
font-weight="500" class="font-sans">Systolic: {{ systolic[i] }}</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
{% set yDia = 280 - (diastolic[i] / 200 * 250) %}
|
||||
<g class="svg-tooltip-group" style="cursor: pointer;">
|
||||
<circle cx="{{ x }}" cy="{{ yDia }}" r="15" fill="transparent" />
|
||||
<circle cx="{{ x }}" cy="{{ yDia }}" r="7" fill="#ffffff" stroke="#ec4899" stroke-width="2.5"
|
||||
class="svg-tooltip-content" />
|
||||
<g class="svg-tooltip-content">
|
||||
<rect x="{{ x - 64 }}" y="{{ yDia - 54 }}" width="128" height="40" fill="#1f2937" rx="6" />
|
||||
<polygon points="{{ x - 6 }},{{ yDia - 14 }} {{ x + 6 }},{{ yDia - 14 }} {{ x }},{{ yDia - 6 }}"
|
||||
fill="#1f2937" />
|
||||
<text x="{{ x }}" y="{{ yDia - 36 }}" font-size="11" fill="#f3f4f6" text-anchor="middle"
|
||||
font-weight="600" class="font-sans">{{ timestamps[i] }}</text>
|
||||
<text x="{{ x }}" y="{{ yDia - 22 }}" font-size="11" fill="#f472b6" text-anchor="middle"
|
||||
font-weight="500" class="font-sans">Diastolic: {{ diastolic[i] }}</text>
|
||||
</g>
|
||||
</g>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Average Lines -->
|
||||
{% set ySysAvg = 280 - (sys_avg / 200 * 250) %}
|
||||
<g class="svg-tooltip-group" style="cursor: default;">
|
||||
<line x1="50" y1="{{ ySysAvg }}" x2="750" y2="{{ ySysAvg }}" stroke="#3b82f6" stroke-width="2"
|
||||
stroke-dasharray="8 6" opacity="0.8" />
|
||||
<line x1="50" y1="{{ ySysAvg }}" x2="750" y2="{{ ySysAvg }}" stroke="transparent"
|
||||
stroke-width="15" /> <!-- Invisible hover anchor -->
|
||||
<g class="svg-tooltip-content">
|
||||
<rect x="336" y="{{ ySysAvg - 34 }}" width="128" height="26" fill="#1f2937" rx="6" />
|
||||
<polygon points="394,{{ ySysAvg - 8 }} 406,{{ ySysAvg - 8 }} 400,{{ ySysAvg - 2 }}"
|
||||
fill="#1f2937" />
|
||||
<text x="400" y="{{ ySysAvg - 17 }}" font-size="11" fill="#93c5fd" text-anchor="middle"
|
||||
font-weight="600" class="font-sans">Avg Systolic: {{ sys_avg }}</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
{% set yDiaAvg = 280 - (dia_avg / 200 * 250) %}
|
||||
<g class="svg-tooltip-group" style="cursor: default;">
|
||||
<line x1="50" y1="{{ yDiaAvg }}" x2="750" y2="{{ yDiaAvg }}" stroke="#ec4899" stroke-width="2"
|
||||
stroke-dasharray="8 6" opacity="0.8" />
|
||||
<line x1="50" y1="{{ yDiaAvg }}" x2="750" y2="{{ yDiaAvg }}" stroke="transparent"
|
||||
stroke-width="15" /> <!-- Invisible hover anchor -->
|
||||
<g class="svg-tooltip-content">
|
||||
<rect x="336" y="{{ yDiaAvg - 34 }}" width="128" height="26" fill="#1f2937" rx="6" />
|
||||
<polygon points="394,{{ yDiaAvg - 8 }} 406,{{ yDiaAvg - 8 }} 400,{{ yDiaAvg - 2 }}"
|
||||
fill="#1f2937" />
|
||||
<text x="400" y="{{ yDiaAvg - 17 }}" font-size="11" fill="#f472b6" text-anchor="middle"
|
||||
font-weight="600" class="font-sans">Avg Diastolic: {{ dia_avg }}</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
{% else %}
|
||||
<rect x="50" y="30" width="700" height="250" fill="#f9fafb" rx="10" />
|
||||
<text x="400" y="155" font-size="16" fill="#9ca3af" text-anchor="middle" font-weight="500">No data
|
||||
available for the selected period.</text>
|
||||
{% endif %}
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Heart Rate Graph Card -->
|
||||
<div class="bg-white rounded-2xl shadow-lg border border-gray-100 p-6 transition-all hover:shadow-xl">
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between mb-6">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-gray-800">Heart Rate</h3>
|
||||
<p class="text-sm text-gray-500 mt-1">Beats per minute over time (bpm)</p>
|
||||
</div>
|
||||
<div class="flex space-x-6 text-sm mt-4 md:mt-0 bg-gray-50 px-4 py-2 rounded-full">
|
||||
<div class="flex items-center"><span
|
||||
class="w-3 h-3 rounded-full bg-emerald-500 mr-2 shadow-sm"></span><span
|
||||
class="font-medium text-gray-700">Heart Rate</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full overflow-x-auto pb-4">
|
||||
<svg viewBox="0 0 800 350" class="w-full min-w-[600px] h-auto drop-shadow-sm font-sans"
|
||||
style="max-height: 350px;">
|
||||
<defs>
|
||||
<linearGradient id="hrGrad" x1="0" x2="0" y1="0" y2="1">
|
||||
<stop offset="0%" stop-color="#10b981" stop-opacity="0.3" />
|
||||
<stop offset="100%" stop-color="#10b981" stop-opacity="0.0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
{% if timestamps %}
|
||||
{% set n = timestamps|length %}
|
||||
{% set spacing = 700 / (n - 1) if n > 1 else 0 %}
|
||||
|
||||
<!-- Horizontal Grid Lines & Y-Axis Labels -->
|
||||
{% for value in range(50, 201, 50) %}
|
||||
{% set y = 280 - (value / 200 * 250) %}
|
||||
<line x1="50" y1="{{ y }}" x2="750" y2="{{ y }}" stroke="#e5e7eb" stroke-width="1"
|
||||
stroke-dasharray="4 4" />
|
||||
<text x="40" y="{{ y + 4 }}" font-size="12" fill="#9ca3af" text-anchor="end" font-weight="500">{{ value
|
||||
}}</text>
|
||||
{% endfor %}
|
||||
|
||||
<!-- X-Axis Base Line -->
|
||||
<line x1="50" y1="280" x2="750" y2="280" stroke="#d1d5db" stroke-width="1" />
|
||||
|
||||
<!-- X-Axis Labels -->
|
||||
{% set ns = namespace(last_x=-100) %}
|
||||
{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
{% set is_last = (i == n - 1) %}
|
||||
{% if x - ns.last_x > 45 or is_last %}
|
||||
{% if not is_last or x - ns.last_x > 40 or ns.last_x == -100 %}
|
||||
<text x="{{ x }}" y="310" font-size="11" fill="#6b7280" text-anchor="middle" font-weight="500"
|
||||
transform="rotate(-25 {{ x }} 310)">{{
|
||||
timestamps[i] }}</text>
|
||||
{% set ns.last_x = x %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<!-- Heart Rate Filled Area -->
|
||||
<polygon fill="url(#hrGrad)" points="{% if n>1 %}50{% else %}400{% endif %},280
|
||||
{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
{% set y = 280 - (heart_rate[i] / 200 * 250) %}
|
||||
{{ x }},{{ y }}
|
||||
{% endfor %}
|
||||
{% if n>1 %}{{ 50 + (n - 1) * spacing }}{% else %}400{% endif %},280" />
|
||||
|
||||
<!-- Heart Rate Line -->
|
||||
<polyline fill="none" stroke="#10b981" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"
|
||||
points="{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
{% set y = 280 - (heart_rate[i] / 200 * 250) %}
|
||||
{{ x }},{{ y }}
|
||||
{% endfor %}" />
|
||||
|
||||
<!-- Heart Rate Points -->
|
||||
{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
{% set y = 280 - (heart_rate[i] / 200 * 250) %}
|
||||
<circle cx="{{ x }}" cy="{{ y }}" r="5" fill="#ffffff" stroke="#10b981" stroke-width="2.5"></circle>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Tooltips (Rendered last to stay on top) -->
|
||||
{% for i in range(n) %}
|
||||
{% set x = 50 + time_percentages[i] * 700 if n > 1 else 400 %}
|
||||
{% set yHR = 280 - (heart_rate[i] / 200 * 250) %}
|
||||
<g class="svg-tooltip-group" style="cursor: pointer;">
|
||||
<circle cx="{{ x }}" cy="{{ yHR }}" r="15" fill="transparent" />
|
||||
<circle cx="{{ x }}" cy="{{ yHR }}" r="7" fill="#ffffff" stroke="#10b981" stroke-width="2.5"
|
||||
class="svg-tooltip-content" />
|
||||
<g class="svg-tooltip-content">
|
||||
<rect x="{{ x - 64 }}" y="{{ yHR - 54 }}" width="128" height="40" fill="#1f2937" rx="6" />
|
||||
<polygon points="{{ x - 6 }},{{ yHR - 14 }} {{ x + 6 }},{{ yHR - 14 }} {{ x }},{{ yHR - 6 }}"
|
||||
fill="#1f2937" />
|
||||
<text x="{{ x }}" y="{{ yHR - 36 }}" font-size="11" fill="#f3f4f6" text-anchor="middle"
|
||||
font-weight="600" class="font-sans">{{ timestamps[i] }}</text>
|
||||
<text x="{{ x }}" y="{{ yHR - 22 }}" font-size="11" fill="#34d399" text-anchor="middle"
|
||||
font-weight="500" class="font-sans">Heart Rate: {{ heart_rate[i] }}</text>
|
||||
</g>
|
||||
</g>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Average Line -->
|
||||
{% set yHrAvg = 280 - (hr_avg / 200 * 250) %}
|
||||
<g class="svg-tooltip-group" style="cursor: default;">
|
||||
<line x1="50" y1="{{ yHrAvg }}" x2="750" y2="{{ yHrAvg }}" stroke="#10b981" stroke-width="2"
|
||||
stroke-dasharray="8 6" opacity="0.8" />
|
||||
<line x1="50" y1="{{ yHrAvg }}" x2="750" y2="{{ yHrAvg }}" stroke="transparent" stroke-width="15" />
|
||||
<!-- Invisible hover anchor -->
|
||||
<g class="svg-tooltip-content">
|
||||
<rect x="336" y="{{ yHrAvg - 34 }}" width="128" height="26" fill="#1f2937" rx="6" />
|
||||
<polygon points="394,{{ yHrAvg - 8 }} 406,{{ yHrAvg - 8 }} 400,{{ yHrAvg - 2 }}"
|
||||
fill="#1f2937" />
|
||||
<text x="400" y="{{ yHrAvg - 17 }}" font-size="11" fill="#34d399" text-anchor="middle"
|
||||
font-weight="600" class="font-sans">Avg Heart Rate: {{ hr_avg }}</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
{% else %}
|
||||
<rect x="50" y="30" width="700" height="250" fill="#f9fafb" rx="10" />
|
||||
<text x="400" y="155" font-size="16" fill="#9ca3af" text-anchor="middle" font-weight="500">No data
|
||||
available for the selected period.</text>
|
||||
{% endif %}
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,79 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<defs>
|
||||
<path id="icon-clock" stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M12 8v4l3 3m9-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" />
|
||||
<path id="icon-chevron" stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||
</defs>
|
||||
</svg>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
{% for reading in readings %}
|
||||
<a href="{{ url_for('reading.edit_reading', reading_id=reading.id) }}"
|
||||
class="bg-white shadow-sm hover:shadow-md rounded-xl p-3 flex justify-between items-center border border-gray-100 transition-all">
|
||||
|
||||
<!-- Left side: Timestamp & BP -->
|
||||
<div>
|
||||
<div class="flex items-center text-gray-400 text-xs mb-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5 mr-1" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" stroke-width="2">
|
||||
<use href="#icon-clock"></use>
|
||||
</svg>
|
||||
<span title="{{ reading.local_timestamp.strftime('%d %b %Y, %I:%M %p') }}">
|
||||
{{ reading.relative_timestamp }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
<span class="text-xl font-bold text-gray-800">{{ reading.systolic }}<span
|
||||
class="text-gray-400 font-normal mx-0.5">/</span>{{ reading.diastolic }}</span>
|
||||
<span class="text-xs text-gray-500 ml-1">mmHg</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right side: Heart Rate & Icon -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="text-right">
|
||||
<span class="text-xs text-gray-500 block mb-0.5">HR</span>
|
||||
<div class="flex items-baseline justify-end">
|
||||
<span class="text-lg font-bold text-gray-700">{{ reading.heart_rate }}</span>
|
||||
<span class="text-xs text-gray-400 ml-1">bpm</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-gray-300">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
|
||||
stroke="currentColor" class="h-4 w-4">
|
||||
<use href="#icon-chevron"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="col-span-full text-center text-sm text-gray-500">
|
||||
No readings found.
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination Controls -->
|
||||
{% if pagination.pages > 1 %}
|
||||
<div class="flex justify-center items-center gap-2 mt-6">
|
||||
{% if pagination.has_prev %}
|
||||
<button hx-get="{{ url_for('main.dashboard_list', page=pagination.prev_num) }}" hx-target="#dashboard-content"
|
||||
class="px-3 py-1 rounded bg-gray-200 hover:bg-gray-300 text-sm">« Prev</button>
|
||||
{% endif %}
|
||||
|
||||
{% for page_num in pagination.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
|
||||
{% if page_num %}
|
||||
<button hx-get="{{ url_for('main.dashboard_list', page=page_num) }}" hx-target="#dashboard-content"
|
||||
class="px-3 py-1 rounded text-sm {% if page_num == pagination.page %}bg-primary-600 text-white{% else %}bg-gray-200 hover:bg-gray-300{% endif %}">
|
||||
{{ page_num }}
|
||||
</button>
|
||||
{% else %}
|
||||
<span class="text-gray-400">…</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if pagination.has_next %}
|
||||
<button hx-get="{{ url_for('main.dashboard_list', page=pagination.next_num) }}" hx-target="#dashboard-content"
|
||||
class="px-3 py-1 rounded bg-gray-200 hover:bg-gray-300 text-sm">Next »</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1,88 +0,0 @@
|
||||
<div class="flex flex-col px-2 py-2 -mb-px">
|
||||
<!-- Monthly Navigation -->
|
||||
<div class="flex justify-between items-center mb-4 px-2 mt-2">
|
||||
<button hx-get="{{ url_for('main.dashboard_monthly', month_offset=month_offset - 1) }}"
|
||||
hx-target="#dashboard-content"
|
||||
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
|
||||
</button>
|
||||
|
||||
<h2 class="text-xl font-bold text-gray-800">{{ target_month_date.strftime('%B %Y') }}</h2>
|
||||
|
||||
<button hx-get="{{ url_for('main.dashboard_monthly', month_offset=month_offset + 1) }}"
|
||||
hx-target="#dashboard-content"
|
||||
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>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-7 pl-2 pr-2">
|
||||
|
||||
<div class="p-2 h-10 text-center font-bold">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Sunday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Sun</span>
|
||||
</div>
|
||||
<div class="p-2 h-10 text-center font-bold">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Monday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Mon</span>
|
||||
</div>
|
||||
<div class="p-2 h-10 text-center font-bold">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Tuesday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Tue</span>
|
||||
</div>
|
||||
<div class="p-2 h-10 text-center font-bold">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Wednesday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Wed</span>
|
||||
</div>
|
||||
<div class="p-2 h-10 text-center font-bold">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Thursday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Thu</span>
|
||||
</div>
|
||||
<div class="p-2 h-10 text-center font-bold">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Friday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Fri</span>
|
||||
</div>
|
||||
<div class="p-2 h-10 text-center font-bold">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Saturday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Sat</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-7 overflow-hidden flex-1 pl-2 pr-2 w-full">
|
||||
|
||||
{% for day in month %}
|
||||
<div
|
||||
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="text-right w-full mb-1">
|
||||
<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>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,172 +0,0 @@
|
||||
<div class="space-y-6">
|
||||
<!-- Table Date Filter -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 mb-2">
|
||||
<form hx-get="{{ url_for('main.dashboard_table') }}" hx-target="#dashboard-content"
|
||||
class="flex flex-col sm:flex-row gap-4">
|
||||
<!-- Start Date -->
|
||||
<div class="flex-1">
|
||||
<label for="start_date" class="block text-sm font-medium text-gray-700 mb-1">Start Date</label>
|
||||
<input type="date" name="start_date" id="start_date" value="{{ start_date or '' }}"
|
||||
class="w-full p-2.5 border border-gray-300 rounded-xl shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-500 text-gray-700">
|
||||
</div>
|
||||
|
||||
<!-- End Date -->
|
||||
<div class="flex-1">
|
||||
<label for="end_date" class="block text-sm font-medium text-gray-700 mb-1">End Date</label>
|
||||
<input type="date" name="end_date" id="end_date" value="{{ end_date or '' }}"
|
||||
class="w-full p-2.5 border border-gray-300 rounded-xl shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-500 text-gray-700">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Data Container -->
|
||||
<div class="bg-transparent md:bg-white md:rounded-2xl md:shadow-sm md:border md:border-gray-100 overflow-hidden">
|
||||
|
||||
<!-- Mobile Card View (Hidden on medium screens and up) -->
|
||||
<div class="md:hidden space-y-4">
|
||||
{% for reading in readings %}
|
||||
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-100 flex flex-col gap-3">
|
||||
<div class="flex justify-between items-center border-b border-gray-50 pb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span class="text-sm font-bold text-gray-800">{{ reading.local_timestamp.strftime('%Y-%m-%d')
|
||||
}}</span>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-gray-500">{{ reading.local_timestamp.strftime('%I:%M %p')
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Blood Pressure</span>
|
||||
<div class="flex items-baseline gap-1">
|
||||
<span
|
||||
class="font-bold text-lg {{ 'text-red-500' if reading.systolic >= 130 else 'text-gray-900' }}">{{
|
||||
reading.systolic }}</span>
|
||||
<span class="font-medium text-gray-400">/</span>
|
||||
<span
|
||||
class="font-bold text-lg {{ 'text-red-500' if reading.diastolic >= 80 else 'text-gray-900' }}">{{
|
||||
reading.diastolic }}</span>
|
||||
<span class="text-xs text-gray-500 font-medium ml-1">mmHg</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Heart Rate</span>
|
||||
<div class="flex items-baseline">
|
||||
<span class="font-bold text-lg text-gray-900">{{ reading.heart_rate }}</span>
|
||||
<span class="text-xs text-gray-500 font-medium ml-1">bpm</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 mt-1 border-t border-gray-50">
|
||||
<a href="{{ url_for('reading.edit_reading', reading_id=reading.id) }}"
|
||||
class="inline-flex items-center gap-1 text-primary-600 hover:text-primary-800 text-sm font-bold transition-colors">
|
||||
Edit
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div
|
||||
class="bg-white p-8 text-center rounded-xl shadow-sm border border-gray-100 flex flex-col items-center gap-3">
|
||||
<svg class="w-12 h-12 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-gray-500">No readings found for this date range.</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Desktop Table View (Hidden on small screens) -->
|
||||
<div class="hidden md:block overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col"
|
||||
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||
Date</th>
|
||||
<th scope="col"
|
||||
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||
Time</th>
|
||||
<th scope="col"
|
||||
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||
Systolic</th>
|
||||
<th scope="col"
|
||||
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||
Diastolic</th>
|
||||
<th scope="col"
|
||||
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||
Heart Rate</th>
|
||||
<th scope="col"
|
||||
class="px-6 py-3 text-right text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||
Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
{% for reading in readings %}
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{
|
||||
reading.local_timestamp.strftime('%Y-%m-%d') }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{
|
||||
reading.local_timestamp.strftime('%I:%M %p') }}</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm font-semibold {{ 'text-red-500' if reading.systolic >= 130 else 'text-gray-900' }}">
|
||||
{{ reading.systolic }}</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm font-semibold {{ 'text-red-500' if reading.diastolic >= 80 else 'text-gray-900' }}">
|
||||
{{ reading.diastolic }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ reading.heart_rate }} bpm</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<a href="{{ url_for('reading.edit_reading', reading_id=reading.id) }}"
|
||||
class="text-primary-600 hover:text-primary-900">Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-12 text-center text-sm text-gray-500">
|
||||
No readings found for this date range.
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination Controls -->
|
||||
{% if pagination.pages > 1 %}
|
||||
<div class="flex justify-center items-center gap-2 mt-6">
|
||||
{% if pagination.has_prev %}
|
||||
<button
|
||||
hx-get="{{ url_for('main.dashboard_table', page=pagination.prev_num, start_date=start_date, end_date=end_date) }}"
|
||||
hx-target="#dashboard-content" class="px-3 py-1 rounded bg-gray-200 hover:bg-gray-300 text-sm">«
|
||||
Prev</button>
|
||||
{% endif %}
|
||||
|
||||
{% for page_num in pagination.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
|
||||
{% if page_num %}
|
||||
<button hx-get="{{ url_for('main.dashboard_table', page=page_num, start_date=start_date, end_date=end_date) }}"
|
||||
hx-target="#dashboard-content"
|
||||
class="px-3 py-1 rounded text-sm {% if page_num == pagination.page %}bg-primary-600 text-white{% else %}bg-gray-200 hover:bg-gray-300{% endif %}">
|
||||
{{ page_num }}
|
||||
</button>
|
||||
{% else %}
|
||||
<span class="text-gray-400">…</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if pagination.has_next %}
|
||||
<button
|
||||
hx-get="{{ url_for('main.dashboard_table', page=pagination.next_num, start_date=start_date, end_date=end_date) }}"
|
||||
hx-target="#dashboard-content" class="px-3 py-1 rounded bg-gray-200 hover:bg-gray-300 text-sm">Next
|
||||
»</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -1,56 +0,0 @@
|
||||
<div>
|
||||
<!-- Weekly Navigation -->
|
||||
<div class="flex justify-between items-center mb-4 px-2">
|
||||
<button hx-get="{{ url_for('main.dashboard_weekly', week_offset=week_offset - 1) }}"
|
||||
hx-target="#dashboard-content"
|
||||
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 Week
|
||||
</button>
|
||||
|
||||
<span class="text-gray-600 font-semibold {% if week_offset == 0 %}text-primary-600{% endif %}">
|
||||
{% 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
|
||||
%} </span>
|
||||
|
||||
<button hx-get="{{ url_for('main.dashboard_weekly', week_offset=week_offset + 1) }}"
|
||||
hx-target="#dashboard-content"
|
||||
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>
|
||||
</button>
|
||||
</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>
|
||||
@@ -0,0 +1,4 @@
|
||||
<div class="range-bar">
|
||||
<nav class="segmented" aria-label="Quick date ranges">{% for days in [7, 30, 90] %}<a href="{{ url_for('main.dashboard', view=view, days=days) }}" {% if active_days == days %}aria-current="page"{% endif %}>{{ days }} days</a>{% endfor %}</nav>
|
||||
<form method="get" action="{{ url_for('main.dashboard') }}" class="date-form"><input type="hidden" name="view" value="{{ view }}"><div class="field"><label for="start_date">From</label><input id="start_date" name="start_date" type="date" value="{{ selected.start }}" required></div><div class="field"><label for="end_date">To</label><input id="end_date" name="end_date" type="date" value="{{ selected.end }}" required></div><button type="submit" class="button">Apply</button></form>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
<section class="card trend-card">
|
||||
<div class="section-heading"><div><p class="eyebrow">THE BIGGER PICTURE</p><h2>Blood pressure trend</h2></div><span class="chart-unit">mmHg</span></div>
|
||||
<div class="chart-legend"><span><i class="legend-line systolic"></i>Systolic</span><span><i class="legend-line diastolic"></i>Diastolic</span>{% if stats.count %}<span class="muted small">Average {{ stats.systolic }} / {{ stats.diastolic }} · {{ stats.count }} readings</span>{% endif %}</div>
|
||||
{% if chart.points %}
|
||||
<svg class="trend-chart" viewBox="0 0 800 280" role="img" aria-labelledby="trend-title trend-desc"><title id="trend-title">Blood pressure over the selected period</title><desc id="trend-desc">{{ chart.bucket_days }}-day averages. Systolic average {{ stats.systolic }}, diastolic average {{ stats.diastolic }} mmHg. Exact averages and counts are available {{ 'in the expandable chart data below' if view == 'trends' else 'on the Trends page' }}.</desc>
|
||||
{% for tick in [50, 100, 150, 200, 250] %}{% set y = 244 - tick / 260 * 208 %}<line class="grid-line" x1="48" x2="752" y1="{{ y }}" y2="{{ y }}"/><text x="34" y="{{ y + 4 }}" text-anchor="end">{{ tick }}</text>{% endfor %}
|
||||
{% for series in chart.paths[:2] %}<path class="chart-line {{ series.key }}" d="{{ series.path }}"/>{% for x,y in series.coordinates %}<circle class="chart-point {{ series.key }}" cx="{{ x }}" cy="{{ y }}" r="3"/>{% endfor %}{% endfor %}
|
||||
<text x="48" y="270">{{ selected.start.strftime('%d %b %Y') }}</text><text x="752" y="270" text-anchor="end">{{ selected.end.strftime('%d %b %Y') }}</text></svg>
|
||||
<div class="chart-caption"><p>{{ 'Daily' if chart.bucket_days == 1 else chart.bucket_days|string ~ '-day' }} averages · {{ timezone_name }}. Gaps without readings are joined.</p>{% if view == 'overview' %}<a class="text-link" href="{{ url_for('main.dashboard', view='trends', **selected.params) }}">Explore trends →</a>{% endif %}</div>
|
||||
{% if view == 'trends' %}
|
||||
<details class="chart-details"><summary>View pulse trend · average {{ stats.pulse }} bpm</summary><svg class="trend-chart" viewBox="0 0 800 280" role="img" aria-labelledby="pulse-title"><title id="pulse-title">Pulse in bpm, {{ chart.bucket_days }}-day averages. Average {{ stats.pulse }} bpm; values available in the chart data table.</title>{% for tick in [50, 100, 150, 200] %}{% set y = 244 - tick / 260 * 208 %}<line class="grid-line" x1="48" x2="752" y1="{{ y }}" y2="{{ y }}"/><text x="34" y="{{ y + 4 }}" text-anchor="end">{{ tick }}</text>{% endfor %}<path class="chart-line" d="{{ chart.paths[2].path }}"/>{% for x,y in chart.paths[2].coordinates %}<circle class="chart-point" cx="{{ x }}" cy="{{ y }}" r="3"/>{% endfor %}<text x="48" y="270">{{ selected.start.strftime('%d %b %Y') }}</text><text x="752" y="270" text-anchor="end">{{ selected.end.strftime('%d %b %Y') }}</text></svg></details>
|
||||
<details class="chart-details"><summary>View chart data & pulse</summary><p class="muted small">Averages are calculated from original readings. Each row covers up to {{ chart.bucket_days }} day(s); pulse is shown in bpm.</p><div class="table-scroll" tabindex="0" role="region" aria-label="Chart data"><table><thead><tr><th scope="col">Period</th><th scope="col">Systolic</th><th scope="col">Diastolic</th><th scope="col">Pulse</th><th scope="col">Readings</th></tr></thead><tbody>{% for point in chart.points %}<tr><th scope="row">{{ point.date }}{% if point.date != point.end %} – {{ point.end }}{% endif %}</th><td>{{ point.systolic }}</td><td>{{ point.diastolic }}</td><td>{{ point.pulse }}</td><td>{{ point.count }}</td></tr>{% endfor %}</tbody></table></div></details>
|
||||
{% endif %}
|
||||
{% else %}<div class="empty-state"><h3>A trend begins with a reading</h3><p>No readings in this period. Try another range or add your first reading.</p></div>{% endif %}
|
||||
</section>
|
||||
Reference in New Issue
Block a user