Enhance health dashboard and sleep details UI

Added new cards for sleep, resting heart rate, and skin temperature deviation to the health dashboard. Expanded HealthSleepListTabsComponent with detailed tabs for heart rate, SpO2, respiratory rate, and additional sleep metrics, including improved handling of missing data. Updated i18n files for new labels and units, and adjusted HealthSideBarComponent navigation order. Minor backend and script fixes for field naming and test data.
This commit is contained in:
João Vitória Silva
2025-11-27 18:17:16 +00:00
parent 19e282ed26
commit 331cc9762c
9 changed files with 371 additions and 123 deletions

View File

@@ -228,11 +228,11 @@ if api:
"from": 1763251200000,
"until": 1763337599999,
"weight": 68199.0,
"bmi": None,
"bodyFat": None,
"bodyWater": None,
"boneMass": None,
"muscleMass": None,
"bmi": 24.10,
"bodyFat": 21.20,
"bodyWater": 57.50,
"boneMass": 4.27,
"muscleMass": 30.40,
"physiqueRating": None,
"visceralFat": None,
"metabolicAge": None,

View File

@@ -805,7 +805,7 @@ def parse_frame_session(frame):
get_value_from_frame(frame, "normalized_power"),
get_value_from_frame(frame, "enhanced_avg_speed"),
get_value_from_frame(frame, "enhanced_max_speed"),
get_value_from_frame(frame, "workout_feeling"),
get_value_from_frame(frame, "workout_feel"),
get_value_from_frame(frame, "workout_rpe"),
)

View File

@@ -1,6 +1,59 @@
<template>
<div class="col">
<div class="row">
<!-- Today's sleep -->
<div class="col-lg-4 col-md-12">
<div class="card mb-3 text-center shadow-sm">
<div class="card-header">
<h4>{{ $t('healthDashboardZoneComponent.sleep') }}</h4>
</div>
<div class="card-body">
<h1 v-if="todaySleep">{{ formatDuration(todaySleep) }}</h1>
<h1 v-else>{{ $t('generalItems.labelNoData') }}</h1>
</div>
<div class="card-footer text-body-secondary">
<span v-if="userHealthTargets && userHealthTargets['sleep']">
<font-awesome-icon :icon="['fas', 'angle-down']" class="me-1"
v-if="todaySleep < userHealthTargets.sleep" />
<font-awesome-icon :icon="['fas', 'angle-up']" class="me-1" v-else />
{{ formatDuration(userHealthTargets.sleep) }}
</span>
<span v-else>{{ $t('healthDashboardZoneComponent.noSleepTarget') }}</span>
</div>
</div>
</div>
<!-- resting heart rate -->
<div class="col-lg-4 col-md-12">
<div class="card mb-3 text-center shadow-sm">
<div class="card-header">
<h4>{{ $t('healthDashboardZoneComponent.restingHeartRate') }}</h4>
</div>
<div class="card-body">
<h1 v-if="restingHeartRate">{{ restingHeartRate }} {{ $t('generalItems.unitsBpm') }}</h1>
<h1 v-else>{{ $t('generalItems.labelNoData') }}</h1>
</div>
<div class="card-footer text-body-secondary">
<span v-if="hrvStatus">{{ $t(getHrvStatusI18nKey(hrvStatus)) }}</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</div>
</div>
</div>
<!-- avg skin temperature deviation -->
<div class="col-lg-4 col-md-12">
<div class="card mb-3 text-center shadow-sm">
<div class="card-header">
<h4>{{ $t('healthDashboardZoneComponent.avgSkinTemperatureDeviation') }}</h4>
</div>
<div class="card-body">
<h1 v-if="avgSkinTempDeviation">{{ avgSkinTempDeviation }} {{ $t('generalItems.unitsCelsius') }}</h1>
<h1 v-else>{{ $t('generalItems.labelNoData') }}</h1>
</div>
<div class="card-footer text-body-secondary">
<span>{{ $t('generalItems.labelNoData') }}</span>
</div>
</div>
</div>
<!-- weight -->
<div class="col-lg-4 col-md-12">
<div class="card mb-3 text-center shadow-sm">
<div class="card-header">
@@ -16,8 +69,9 @@
<h1 v-else>{{ $t('generalItems.labelNotApplicable') }}</h1>
</div>
<div class="card-footer text-body-secondary">
<font-awesome-icon :icon="['fas', 'angle-down']" class="me-1" v-if="currentWeight > userHealthTargets.weight" />
<font-awesome-icon :icon="['fas', 'angle-up']" class="me-1" v-else/>
<font-awesome-icon :icon="['fas', 'angle-down']" class="me-1"
v-if="currentWeight > userHealthTargets.weight" />
<font-awesome-icon :icon="['fas', 'angle-up']" class="me-1" v-else />
<span v-if="userHealthTargets && userHealthTargets['weight'] && Number(authStore?.user?.units) === 1">
{{ userHealthTargets.weight }} {{ $t('generalItems.unitsKg') }}
</span>
@@ -28,6 +82,7 @@
</div>
</div>
</div>
<!-- BMI -->
<div class="col-lg-4 col-md-12">
<div class="card mb-3 text-center shadow-sm">
<div class="card-header">
@@ -41,11 +96,12 @@
<span v-if="currentBMI">{{ bmiDescription }}</span>
<span v-else-if="!currentBMI && currentWeight">{{
$t('healthDashboardZoneComponent.noHeightDefined')
}}</span>
}}</span>
<span v-else>{{ $t('healthDashboardZoneComponent.noWeightData') }}</span>
</div>
</div>
</div>
<!-- Today's steps -->
<div class="col-lg-4 col-md-12">
<div class="card mb-3 text-center shadow-sm">
<div class="card-header">
@@ -57,8 +113,9 @@
</div>
<div class="card-footer text-body-secondary">
<span v-if="userHealthTargets && userHealthTargets['steps']">
<font-awesome-icon :icon="['fas', 'angle-down']" class="me-1" v-if="todaySteps < userHealthTargets.steps" />
<font-awesome-icon :icon="['fas', 'angle-up']" class="me-1" v-else/>
<font-awesome-icon :icon="['fas', 'angle-down']" class="me-1"
v-if="todaySteps < userHealthTargets.steps" />
<font-awesome-icon :icon="['fas', 'angle-up']" class="me-1" v-else />
{{ userHealthTargets.steps }} {{ $t('healthDashboardZoneComponent.stepsTargetLabel') }}
</span>
<span v-else>{{ $t('healthDashboardZoneComponent.noStepsTarget') }}</span>
@@ -75,6 +132,8 @@ import { useI18n } from 'vue-i18n'
// Importing the stores
import { useAuthStore } from '@/stores/authStore'
import { kgToLbs } from '@/utils/unitsUtils'
import { formatDuration } from '@/utils/dateTimeUtils'
import { getHrvStatusI18nKey } from '@/utils/healthUtils'
const props = defineProps({
userHealthWeight: {
@@ -85,6 +144,10 @@ const props = defineProps({
type: [Object, null],
required: true
},
userHealthSleep: {
type: [Object, null],
required: true
},
userHealthTargets: {
type: [Object, null],
required: true
@@ -97,6 +160,10 @@ const currentWeight = ref(null)
const currentBMI = ref(null)
const bmiDescription = ref(null)
const todaySteps = ref(null)
const todaySleep = ref(null)
const restingHeartRate = ref(null)
const hrvStatus = ref(null)
const avgSkinTempDeviation = ref(null)
onMounted(async () => {
if (props.userHealthWeight) {
@@ -132,5 +199,16 @@ onMounted(async () => {
}
}
}
if (props.userHealthSleep) {
for (const data of props.userHealthSleep) {
if (data.total_sleep_seconds) {
todaySleep.value = data.total_sleep_seconds
restingHeartRate.value = data.resting_heart_rate
hrvStatus.value = data.hrv_status
avgSkinTempDeviation.value = data.avg_skin_temp_deviation
break
}
}
}
})
</script>

View File

@@ -1,54 +1,35 @@
<template>
<div class="col-lg-3 col-md-12">
<ul
class="nav nav-pills flex-column mb-auto bg-body-tertiary rounded p-3 shadow-sm"
id="sidebarNav"
>
<ul class="nav nav-pills flex-column mb-auto bg-body-tertiary rounded p-3 shadow-sm" id="sidebarNav">
<li class="nav-item">
<a
href="#"
class="nav-link link-body-emphasis"
:class="{ active: activeSection === 'dashboard' }"
@click.prevent="changeActive('dashboard')"
>
<a href="#" class="nav-link link-body-emphasis" :class="{ active: activeSection === 'dashboard' }"
@click.prevent="changeActive('dashboard')">
<font-awesome-icon :icon="['fas', 'dashboard']" />
<span class="ms-1">{{ $t('healthSideBarComponent.dashboardSection') }}</span>
</a>
</li>
<hr />
<li class="nav-item">
<a
href="#"
class="nav-link link-body-emphasis"
:class="{ active: activeSection === 'weight' }"
@click.prevent="changeActive('weight')"
>
<a href="#" class="nav-link link-body-emphasis" :class="{ active: activeSection === 'sleep' }"
@click.prevent="changeActive('sleep')">
<font-awesome-icon :icon="['fas', 'bed']" />
<span class="ms-1">{{ $t('healthSideBarComponent.sleepSection') }}</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link link-body-emphasis" :class="{ active: activeSection === 'weight' }"
@click.prevent="changeActive('weight')">
<font-awesome-icon :icon="['fas', 'weight']" />
<span class="ms-2">{{ $t('healthSideBarComponent.weightSection') }}</span>
</a>
</li>
<li class="nav-item">
<a
href="#"
class="nav-link link-body-emphasis"
:class="{ active: activeSection === 'steps' }"
@click.prevent="changeActive('steps')"
>
<a href="#" class="nav-link link-body-emphasis" :class="{ active: activeSection === 'steps' }"
@click.prevent="changeActive('steps')">
<font-awesome-icon :icon="['fas', 'shoe-prints']" />
<span class="ms-1">{{ $t('healthSideBarComponent.stepsSection') }}</span>
</a>
</li>
<li class="nav-item">
<a
href="#"
class="nav-link link-body-emphasis"
:class="{ active: activeSection === 'sleep' }"
@click.prevent="changeActive('sleep')"
>
<font-awesome-icon :icon="['fas', 'bed']" />
<span class="ms-1">{{ $t('healthSideBarComponent.sleepSection') }}</span>
</a>
</li>
</ul>
</div>
</template>

View File

@@ -3,16 +3,17 @@
<ul class="nav nav-tabs mt-3" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active link-body-emphasis link-underline-opacity-0 link-underline-opacity-100-hover"
:id="`sessions-tab-${userHealthSleep.id}`" data-bs-toggle="tab"
:data-bs-target="`#sessions-${userHealthSleep.id}`" type="button" role="tab"
:aria-controls="`sessions-${userHealthSleep.id}`" aria-selected="true">
:id="`sleep-score-tab-${userHealthSleep.id}`" data-bs-toggle="tab"
:data-bs-target="`#sleep-score-${userHealthSleep.id}`" type="button" role="tab"
:aria-controls="`sleep-score-${userHealthSleep.id}`" aria-selected="true">
{{ $t('healthSleepListTabsComponent.sleepScoreLabel') }}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link link-body-emphasis link-underline-opacity-0 link-underline-opacity-100-hover"
:id="`idps-tab-${userHealthSleep.id}`" data-bs-toggle="tab" :data-bs-target="`#idps-${userHealthSleep.id}`"
type="button" role="tab" :aria-controls="`idps-${userHealthSleep.id}`" aria-selected="false">
:id="`sleep-details-tab-${userHealthSleep.id}`" data-bs-toggle="tab"
:data-bs-target="`#sleep-details-${userHealthSleep.id}`" type="button" role="tab"
:aria-controls="`sleep-details-${userHealthSleep.id}`" aria-selected="false">
{{ $t('healthSleepListTabsComponent.sleepDetailsLabel') }}
</button>
</li>
@@ -21,8 +22,8 @@
<!-- Tab Content -->
<div class="tab-content mt-3">
<!-- Score tab -->
<div class="tab-pane fade show active" :id="`sessions-${userHealthSleep.id}`" role="tabpanel"
:aria-labelledby="`sessions-tab-${userHealthSleep.id}`">
<div class="tab-pane fade show active" :id="`sleep-score-${userHealthSleep.id}`" role="tabpanel"
:aria-labelledby="`sleep-score-tab-${userHealthSleep.id}`">
<!-- Sleep summary -->
<section class="pb-3 mb-3 border-bottom">
<h6 class="fw-semibold mb-2">
@@ -30,32 +31,42 @@
</h6>
<div class="row">
<div class="col-12 col-md-6">
<p v-if="userHealthSleep.sleep_score_overall" class="mb-1">
<!-- sleep_score_overall -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.scoreLabel') }}:
</span>
{{ userHealthSleep.sleep_score_overall }}
<span v-if="userHealthSleep.sleep_score_overall">{{ userHealthSleep.sleep_score_overall }}</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
<p v-if="userHealthSleep.sleep_score_quality" class="mb-1">
<!-- sleep_score_quality -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.qualityLabel') }}:
</span>
{{ $t(getScoreStatusI18nKey(userHealthSleep.sleep_score_quality)) }}
<span v-if="userHealthSleep.sleep_score_quality">{{
$t(getScoreStatusI18nKey(userHealthSleep.sleep_score_quality)) }}</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
</div>
<div class="col-12 col-md-6">
<p v-if="userHealthSleep.sleep_score_duration" class="mb-1">
<!-- sleep_score_duration-->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.durationLabel') }}:
</span>
{{ $t(getScoreStatusI18nKey(userHealthSleep.sleep_score_duration)) }}
<span v-if="userHealthSleep.sleep_score_duration">{{
$t(getScoreStatusI18nKey(userHealthSleep.sleep_score_duration)) }}</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
<p v-if="userHealthSleep.hrv_status" class="mb-0">
<!-- hrv_status -->
<p class="mb-0">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.HRVLabel') }}:
</span>
{{ $t(getHrvStatusI18nKey(userHealthSleep.hrv_status)) }}
<span v-if="userHealthSleep.hrv_status">{{ $t(getHrvStatusI18nKey(userHealthSleep.hrv_status)) }}</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
</div>
</div>
@@ -68,40 +79,228 @@
</h6>
<div class="row">
<div class="col-12 col-md-6">
<!-- deep_sleep_seconds -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.deepLabel') }}:
</span>
{{ formatDuration(userHealthSleep.deep_sleep_seconds) }}
-
{{ $t(getScoreStatusI18nKey(userHealthSleep.deep_percentage_score)) }}
<span v-if="userHealthSleep.deep_sleep_seconds">
{{ formatDuration(userHealthSleep.deep_sleep_seconds) }}
-
{{ $t(getScoreStatusI18nKey(userHealthSleep.deep_percentage_score)) }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
<!-- rem_sleep_seconds-->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.REMLabel') }}:
</span>
{{ formatDuration(userHealthSleep.rem_sleep_seconds) }}
-
{{ $t(getScoreStatusI18nKey(userHealthSleep.rem_percentage_score)) }}
<span v-if="userHealthSleep.rem_sleep_seconds">
{{ formatDuration(userHealthSleep.rem_sleep_seconds) }}
-
{{ $t(getScoreStatusI18nKey(userHealthSleep.rem_percentage_score)) }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
</div>
<div class="col-12 col-md-6">
<!-- light_sleep_seconds -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.lightLabel') }}:
</span>
{{ formatDuration(userHealthSleep.light_sleep_seconds) }}
-
{{ $t(getScoreStatusI18nKey(userHealthSleep.light_percentage_score)) }}
<span v-if="userHealthSleep.light_sleep_seconds">
{{ formatDuration(userHealthSleep.light_sleep_seconds) }}
-
{{ $t(getScoreStatusI18nKey(userHealthSleep.light_percentage_score)) }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
<!-- awake_sleep_seconds -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.awakeLabel') }}:
</span>
{{ formatDuration(userHealthSleep.awake_sleep_seconds) }}
-
{{ $t(getScoreStatusI18nKey(userHealthSleep.awake_count_score)) }}
<span v-if="userHealthSleep.awake_sleep_seconds">
{{ formatDuration(userHealthSleep.awake_sleep_seconds) }}
-
{{ $t(getScoreStatusI18nKey(userHealthSleep.awake_count_score)) }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
</div>
</div>
</section>
</div>
<!-- Sleep Details Tab -->
<div class="tab-pane fade" :id="`sleep-details-${userHealthSleep.id}`" role="tabpanel"
:aria-labelledby="`sleep-details-tab-${userHealthSleep.id}`">
<!-- Heart Rate -->
<section class="pb-3 mb-3 border-bottom">
<div class="row">
<div class="col-12 col-md-6">
<!-- resting_heart_rate -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.restingHeartRateLabel') }}:
</span>
<span v-if="userHealthSleep.resting_heart_rate">
{{ Number(userHealthSleep.resting_heart_rate) }} {{ $t('generalItems.unitsBpm') }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
<!-- avg_skin_temp_deviation -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.avgSkinTempDeviationLabel') }}:
</span>
<span v-if="userHealthSleep.avg_skin_temp_deviation">
{{ parseFloat(userHealthSleep.avg_skin_temp_deviation) }} {{ $t('generalItems.unitsCelsius') }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
</div>
<div class="col-12 col-md-6">
<!-- avg_sleep_stress -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.avgSleepStressLabel') }}:
</span>
<span v-if="userHealthSleep.avg_sleep_stress">
{{ Number(userHealthSleep.avg_sleep_stress) }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
</div>
</div>
</section>
<!-- Heart Rate -->
<section class="pb-3 mb-3 border-bottom">
<h6 class="fw-semibold mb-2">
{{ $t('healthSleepListTabsComponent.heartRateTitle') }}
</h6>
<div class="row">
<div class="col-12 col-md-6">
<!-- avg_heart_rate -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.avgHeartRateLabel') }}:
</span>
<span v-if="userHealthSleep.avg_heart_rate">
{{ Number(userHealthSleep.avg_heart_rate) }} {{ $t('generalItems.unitsBpm') }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
<!-- max_heart_rate -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.maxHeartRateLabel') }}:
</span>
<span v-if="userHealthSleep.max_heart_rate">
{{ Number(userHealthSleep.max_heart_rate) }} {{ $t('generalItems.unitsBrpm') }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
</div>
<div class="col-12 col-md-6">
<!-- min_heart_rate -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.minHeartRateLabel') }}:
</span>
<span v-if="userHealthSleep.min_heart_rate">
{{ Number(userHealthSleep.min_heart_rate) }} {{ $t('generalItems.unitsBrpm') }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
</div>
</div>
</section>
<!-- SpO2 -->
<section class="pb-3 mb-3 border-bottom">
<h6 class="fw-semibold mb-2">
{{ $t('healthSleepListTabsComponent.spo2Title') }}
</h6>
<div class="row">
<div class="col-12 col-md-6">
<!-- avg_spo2 -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.avgSpo2Label') }}:
</span>
<span v-if="userHealthSleep.avg_spo2">
{{ Number(userHealthSleep.avg_spo2) }}%
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
<!-- highest_spo2 -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.maxSpo2Label') }}:
</span>
<span v-if="userHealthSleep.highest_spo2">
{{ Number(userHealthSleep.highest_spo2) }}%
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
</div>
<div class="col-12 col-md-6">
<!-- lowest_spo2 -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.minSpo2Label') }}:
</span>
<span v-if="userHealthSleep.lowest_spo2">
{{ Number(userHealthSleep.lowest_spo2) }}%
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
</div>
</div>
</section>
<!-- Respiratory Rate -->
<section class="pb-3 mb-3 border-bottom">
<h6 class="fw-semibold mb-2">
{{ $t('healthSleepListTabsComponent.respiratoryTitle') }}
</h6>
<div class="row">
<div class="col-12 col-md-6">
<!-- avg_respiration -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.avgRespiratoryRateLabel') }}:
</span>
<span v-if="userHealthSleep.avg_respiration">
{{ Number(userHealthSleep.avg_respiration) }} {{ $t('generalItems.unitsBrpm') }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
<!-- highest_respiration -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.maxRespiratoryRateLabel') }}:
</span>
<span v-if="userHealthSleep.highest_respiration">
{{ Number(userHealthSleep.highest_respiration) }} {{ $t('generalItems.unitsBrpm') }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
</div>
<div class="col-12 col-md-6">
<!-- lowest_respiration -->
<p class="mb-1">
<span class="fw-semibold">
{{ $t('healthSleepListTabsComponent.minRespiratoryRateLabel') }}:
</span>
<span v-if="userHealthSleep.lowest_respiration">
{{ Number(userHealthSleep.lowest_respiration) }} {{ $t('generalItems.unitsBrpm') }}
</span>
<span v-else>{{ $t('generalItems.labelNoData') }}</span>
</p>
</div>
</div>
@@ -111,7 +310,7 @@
</template>
<script setup>
import { formatDuration, formatDateShort } from '@/utils/dateTimeUtils'
import { formatDuration } from '@/utils/dateTimeUtils'
import { getHrvStatusI18nKey, getScoreStatusI18nKey } from '@/utils/healthUtils'
const props = defineProps({

View File

@@ -1,4 +1,7 @@
{
"sleep": "Today's sleep",
"restingHeartRate": "Resting heart rate",
"avgSkinTemperatureDeviation": "Skin temp. dev.",
"weight": "Weight",
"noWeightData": "No weight data",
"noWeightTarget": "No weight target",

View File

@@ -17,5 +17,20 @@
"poorLabel": "Poor",
"excellentLabel": "Excellent",
"goodLabel": "Good",
"fairLabel": "Fair"
"fairLabel": "Fair",
"heartRateTitle": "Heart Rate",
"restingHeartRateLabel": "Resting",
"avgHeartRateLabel": "Avg.",
"maxHeartRateLabel": "Max.",
"minHeartRateLabel": "Min.",
"respiratoryTitle": "Respiratory Rate",
"avgRespiratoryRateLabel": "Avg.",
"maxRespiratoryRateLabel": "Max.",
"minRespiratoryRateLabel": "Min.",
"spo2Title": "Blood Oxygen Saturation (SpO2)",
"avgSpo2Label": "Avg.",
"maxSpo2Label": "Max.",
"minSpo2Label": "Min.",
"avgSkinTempDeviationLabel": "Avg. Skin Temp. Deviation",
"avgSleepStressLabel": "Avg. Sleep Stress"
}

View File

@@ -89,5 +89,8 @@
"genderFemale": "Female",
"genderUnspecified": "Unspecified",
"labelAverage": "Average",
"labelMaximum": "Maximum"
"labelMaximum": "Maximum",
"unitsBrpm": "brpm",
"unitsCelsius": "°C",
"unitsFahrenheit": "°F"
}

View File

@@ -2,65 +2,34 @@
<h1>{{ $t('healthView.title') }}</h1>
<div class="row row-gap-3">
<!-- Include the HealthSideBarComponent -->
<HealthSideBarComponent
:activeSection="activeSection"
@update-active-section="updateActiveSection"
/>
<HealthSideBarComponent :activeSection="activeSection" @update-active-section="updateActiveSection" />
<LoadingComponent v-if="isLoading" />
<!-- Include the HealthDashboardZone -->
<HealthDashboardZone
:userHealthWeight="userHealthWeight"
:userHealthSteps="userHealthSteps"
:userHealthTargets="userHealthTargets"
v-if="activeSection === 'dashboard' && !isLoading"
/>
<HealthDashboardZone :userHealthWeight="userHealthWeight" :userHealthSteps="userHealthSteps"
:userHealthSleep="userHealthSleep" :userHealthTargets="userHealthTargets"
v-if="activeSection === 'dashboard' && !isLoading" />
<!-- Include the HealthWeightZone -->
<HealthWeightZone
:userHealthWeight="userHealthWeight"
:userHealthWeightPagination="userHealthWeightPagination"
:userHealthTargets="userHealthTargets"
:isLoading="isLoading"
:totalPages="totalPagesWeight"
:pageNumber="pageNumberWeight"
@createdWeight="updateWeightListAdded"
@deletedWeight="updateWeightListDeleted"
@editedWeight="updateWeightListEdited"
@pageNumberChanged="setPageNumberWeight"
@setWeightTarget="setWeightTarget"
v-if="activeSection === 'weight' && !isLoading"
/>
<HealthWeightZone :userHealthWeight="userHealthWeight" :userHealthWeightPagination="userHealthWeightPagination"
:userHealthTargets="userHealthTargets" :isLoading="isLoading" :totalPages="totalPagesWeight"
:pageNumber="pageNumberWeight" @createdWeight="updateWeightListAdded" @deletedWeight="updateWeightListDeleted"
@editedWeight="updateWeightListEdited" @pageNumberChanged="setPageNumberWeight" @setWeightTarget="setWeightTarget"
v-if="activeSection === 'weight' && !isLoading" />
<!-- Include the HealthStepsZone -->
<HealthStepsZone
:userHealthSteps="userHealthSteps"
:userHealthStepsPagination="userHealthStepsPagination"
:userHealthTargets="userHealthTargets"
:isLoading="isLoading"
:totalPages="totalPagesSteps"
:pageNumber="pageNumberSteps"
@createdSteps="updateStepsListAdded"
@deletedSteps="updateStepsListDeleted"
@editedSteps="updateStepsListEdited"
@pageNumberChanged="setPageNumberSteps"
@setStepsTarget="setStepsTarget"
v-if="activeSection === 'steps' && !isLoading"
/>
<HealthStepsZone :userHealthSteps="userHealthSteps" :userHealthStepsPagination="userHealthStepsPagination"
:userHealthTargets="userHealthTargets" :isLoading="isLoading" :totalPages="totalPagesSteps"
:pageNumber="pageNumberSteps" @createdSteps="updateStepsListAdded" @deletedSteps="updateStepsListDeleted"
@editedSteps="updateStepsListEdited" @pageNumberChanged="setPageNumberSteps" @setStepsTarget="setStepsTarget"
v-if="activeSection === 'steps' && !isLoading" />
<!-- Include the HealthSleepZone -->
<HealthSleepZone
:userHealthSleep="userHealthSleep"
:userHealthSleepPagination="userHealthSleepPagination"
:userHealthTargets="userHealthTargets"
:isLoading="isLoading"
:totalPages="totalPagesSleep"
:pageNumber="pageNumberSleep"
@pageNumberChanged="setPageNumberSleep"
@setSleepTarget="setSleepTarget"
v-if="activeSection === 'sleep' && !isLoading"
/>
<HealthSleepZone :userHealthSleep="userHealthSleep" :userHealthSleepPagination="userHealthSleepPagination"
:userHealthTargets="userHealthTargets" :isLoading="isLoading" :totalPages="totalPagesSleep"
:pageNumber="pageNumberSleep" @pageNumberChanged="setPageNumberSleep" @setSleepTarget="setSleepTarget"
v-if="activeSection === 'sleep' && !isLoading" />
</div>
<!-- back button -->
<BackButtonComponent />