From 1df24000a3751fc56e139bb244d6d22365cff762 Mon Sep 17 00:00:00 2001 From: 4aiman Date: Sat, 17 Oct 2026 14:53:21 +0300 Subject: [PATCH] Track detail: HR chart as its own collapsible section + 'what if by HR' kcal comparison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Speed/Elevation/Пульс are now collapsible
sections; the HR chart moved out of the Elevation block into its own 'Пульс' section (avg/max + chart), shown when the track has HR. - Калории now also shows the Keytel HR-based kcal as a COMPARISON when HR is present ('По ЧСС (сравнение)'). The activity balance keeps the physics/MET number unchanged - this is only to see the difference. --- frontend/js/main.js | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/frontend/js/main.js b/frontend/js/main.js index 51a7bd6..8a971b3 100644 --- a/frontend/js/main.js +++ b/frontend/js/main.js @@ -5468,6 +5468,21 @@ async function showSessionDetail(sessionId) { : "—"; let kcalMin = stats.duration > 0 ? (stats.kcal / (stats.duration / 60)).toFixed(1) : "—"; + // HR-based kcal "what if" (Keytel) — comparison ONLY. The activity balance keeps the physics/MET + // number (don't disturb the established diet); here we just show what HR would give, if present. + let _t0hr = points.length ? new Date(points[0].ts).getTime() : 0; + let hrSamples = points.filter(p => p.heart_rate > 0).map(p => [(new Date(p.ts).getTime() - _t0hr) / 1000, p.heart_rate]); + let hasHr = hrSamples.length > 2; + let hrProfile = { + weightKg: session.rider_weight_kg ?? state.latestWeight?.weight_kg ?? state.profile?.weight_kg ?? 75, + age: BMR.getAge(parseInt(state.profile?.birth_year) || 1988), + sex: state.profile?.sex || "m", + heightCm: parseFloat(state.profile?.height_cm) || 178, + }; + let hrKcal = hasHr ? GPS.hrKcalNet(hrSamples, hrProfile) : null; + let hrAvg = hasHr ? Math.round(hrSamples.reduce((a, s) => a + s[1], 0) / hrSamples.length) : null; + let hrMax = hasHr ? Math.max(...hrSamples.map(s => s[1])) : null; + showModal(`

${actName}

ID: ${session.id}
@@ -5493,27 +5508,29 @@ async function showSessionDetail(sessionId) {
-
-
Скорость
+
+ Скорость
${stats.avgSpeed} км/чСредняя
${stats.maxSpeed} км/чМакс.
-
+
Калории
-
${session.total_kcal ?? stats.kcal} ккалВсего
+
${session.total_kcal ?? stats.kcal} ккал${hasHr ? "Физика (в баланс)" : "Всего"}
${kcalMin} ккал/минРасход
+ ${hasHr ? `
${hrKcal} ккалПо ЧСС (сравнение)
+
В баланс идёт «физика»; цифра по ЧСС (Keytel) - для сравнения.
` : ""}
${elevations.length > 0 ? ` -
-
Высота
+
+ Высота
${minElev} мМин.
${maxElev} мМакс.
@@ -5523,8 +5540,17 @@ async function showSessionDetail(sessionId) {
${stats.elevLoss} мСпуск
- -
` : ""} + ` : ""} + + ${hasHr ? ` +
+ Пульс +
+
${hrAvg}Средний
+
${hrMax}Макс.
+
+
+
` : ""} ${(stats.segments && stats.segments.length > 1) ? `