162 lines
10 KiB
HTML
162 lines
10 KiB
HTML
{% extends "base.html" %}
|
||
{% from "_trainings_table.html" import approach_chips %}
|
||
|
||
{% block content %}
|
||
{% macro num(value) %}{{ '%g'|format(value|round(1)) }}{% endmacro %}
|
||
{# "+12 кг (+6.5%)" colored by direction; unit is "кг" or "повт." #}
|
||
{% macro change(value, percent, unit) %}
|
||
<span class="{% if value > 0 %}text-success{% elif value < 0 %}text-danger{% else %}text-muted{% endif %}">
|
||
{{ '%+g'|format(value|round(1)) }} {{ unit }}{% if percent is not none %} ({{ '%+.1f'|format(percent) }}%){% endif %}
|
||
</span> к первой тренировке
|
||
{% endmacro %}
|
||
{# Text with a dotted underline and a tooltip explaining it #}
|
||
{% macro tip(text, hint) %}<span class="has-tip" data-bs-toggle="tooltip" title="{{ hint }}">{{ text }}</span>{% endmacro %}
|
||
{% macro stat_card(title, value, note, hint) %}
|
||
<div class="col">
|
||
<div class="card h-100 mb-0">
|
||
<div class="card-body">
|
||
<div class="text-muted small">{{ tip(title, hint) }}</div>
|
||
<div class="fs-4 fw-semibold">{{ value }}</div>
|
||
<div class="text-muted small">{{ note }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endmacro %}
|
||
{% set hints = {
|
||
'record_e1rm': 'e1RM — расчётный максимум на один повтор по лучшему подходу тренировки (формула Эпли: вес × (1 + повторы / 30)). Рекорд — наибольшее значение за всю историю.',
|
||
'last_e1rm': 'e1RM лучшего подхода последней тренировки. Ниже — изменение относительно первой тренировки с этим упражнением.',
|
||
'record_volume': 'Тоннаж — суммарная работа за тренировку: сумма вес × повторы по всем подходам с весом. Рекорд — тренировка с наибольшим тоннажем.',
|
||
'max_weight': 'Самый тяжёлый вес в одном подходе за всю историю, без учёта повторов.',
|
||
'best_reps': 'Наибольшее число повторов в одном подходе за всю историю.',
|
||
'last_best_reps': 'Наибольшее число повторов в одном подходе последней тренировки. Ниже — изменение относительно первой тренировки.',
|
||
'record_total_reps': 'Наибольшая сумма повторов по всем подходам за одну тренировку.',
|
||
'e1rm': 'Расчётный максимум на один повтор по лучшему подходу тренировки (формула Эпли: вес × (1 + повторы / 30)). Подходы без веса не учитываются.',
|
||
'volume': 'Тоннаж: сумма вес × повторы по подходам с весом за тренировку.',
|
||
} %}
|
||
|
||
<h1 class="h3 mb-3">История упражнения</h1>
|
||
|
||
<form method="get" action="{{ url_for('exercises') }}" class="mb-4">
|
||
<label for="exercise-input" class="form-label">Упражнение</label>
|
||
<div class="input-group">
|
||
<input type="search" id="exercise-input" name="name" class="form-control" list="exercise-names"
|
||
value="{{ selected or query }}" placeholder="Начните вводить название…"{% if not query %} autofocus{% endif %}>
|
||
<button type="submit" class="btn btn-primary">Показать</button>
|
||
</div>
|
||
<datalist id="exercise-names">
|
||
{% for item in exercise_names %}<option value="{{ item.name }}"></option>{% endfor %}
|
||
</datalist>
|
||
</form>
|
||
|
||
{% if selected %}
|
||
{% set rows = progress.rows %}
|
||
<h2 class="h4">{{ selected }}</h2>
|
||
<p class="text-muted">
|
||
Тренировок: <strong>{{ rows|length }}</strong>,
|
||
первая: <strong>{{ rows[-1].session.date.strftime('%d.%m.%Y') }}</strong>,
|
||
последняя: <strong>{{ rows[0].session.date.strftime('%d.%m.%Y') }}</strong>
|
||
</p>
|
||
|
||
<div class="row row-cols-1 row-cols-sm-2 row-cols-xl-4 g-3 mb-4">
|
||
{% if progress.weighted %}
|
||
{{ stat_card('Рекорд e1RM', num(progress.best_primary.value) ~ ' кг', progress.best_primary.date.strftime('%d.%m.%Y'), hints.record_e1rm) }}
|
||
{{ stat_card('Последний e1RM', num(progress.last_primary) ~ ' кг',
|
||
change(progress.primary_change, progress.primary_change_percent, 'кг') if rows|length > 1 else '',
|
||
hints.last_e1rm) }}
|
||
{{ stat_card('Рекорд тоннажа', num(progress.best_secondary.value) ~ ' кг', progress.best_secondary.date.strftime('%d.%m.%Y'), hints.record_volume) }}
|
||
{{ stat_card('Максимальный вес', num(progress.max_weight) ~ ' кг', '', hints.max_weight) }}
|
||
{% else %}
|
||
{{ stat_card('Лучший подход', num(progress.best_primary.value) ~ ' повт.', progress.best_primary.date.strftime('%d.%m.%Y'), hints.best_reps) }}
|
||
{{ stat_card('Лучший подход в последний раз', num(progress.last_primary) ~ ' повт.',
|
||
change(progress.primary_change, progress.primary_change_percent, 'повт.') if rows|length > 1 else '',
|
||
hints.last_best_reps) }}
|
||
{{ stat_card('Рекорд суммы повторов', num(progress.best_secondary.value), progress.best_secondary.date.strftime('%d.%m.%Y'), hints.record_total_reps) }}
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="card mb-2">
|
||
<div class="card-body">
|
||
<div style="height: 320px"><canvas id="progress-chart"></canvas></div>
|
||
</div>
|
||
</div>
|
||
<script type="application/json" id="progress-chart-data">{{ chart|tojson }}</script>
|
||
<p class="text-muted small mb-4">
|
||
{% if progress.weighted %}
|
||
e1RM — расчётный максимум на один повтор по формуле Эпли: вес × (1 + повторы / 30), берётся лучший подход
|
||
тренировки; подходы без веса не учитываются. Тоннаж — сумма вес × повторы по подходам с весом.
|
||
{% else %}
|
||
Упражнение выполняется без дополнительного веса, поэтому прогресс считается по повторам.
|
||
{% endif %}
|
||
</p>
|
||
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-hover align-middle" id="exercise-history-table">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">Дата</th>
|
||
<th scope="col">Тренер</th>
|
||
<th scope="col">Подходы (вес × повторения)</th>
|
||
{% if progress.weighted %}
|
||
<th scope="col" class="text-end">Макс. вес</th>
|
||
<th scope="col" class="text-end">{{ tip('e1RM', hints.e1rm) }}</th>
|
||
<th scope="col" class="text-end">{{ tip('Тоннаж', hints.volume) }}</th>
|
||
{% else %}
|
||
<th scope="col" class="text-end">Лучший подход</th>
|
||
<th scope="col" class="text-end">Всего повторов</th>
|
||
{% endif %}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for row in rows %}
|
||
{% set item = row.session %}
|
||
{% set m = row.metrics %}
|
||
<tr>
|
||
{# data-order: ISO date and plain numbers, so sorting is chronological/numeric #}
|
||
<td class="text-nowrap date" data-order="{{ item.date.isoformat() }}">{{ item.date.strftime('%d.%m.%Y') }}</td>
|
||
<td class="text-nowrap">{{ item.trainer or '' }}</td>
|
||
<td>
|
||
{{ approach_chips(item.approaches) }}
|
||
{% if item.splitted_weight %}
|
||
<span class="badge text-bg-light border" title="Вес указан на каждую руку (гантель)">на руку</span>
|
||
{% endif %}
|
||
</td>
|
||
{% if progress.weighted %}
|
||
<td class="text-nowrap text-end" data-order="{{ m.max_weight }}">{% if m.max_weight %}{{ num(m.max_weight) }} кг{% else %}—{% endif %}</td>
|
||
<td class="text-nowrap text-end" data-order="{{ m.e1rm or 0 }}">{% if m.e1rm %}{{ num(m.e1rm) }} кг{% else %}—{% endif %}</td>
|
||
<td class="text-nowrap text-end" data-order="{{ m.volume }}">{% if m.volume %}{{ num(m.volume) }} кг{% else %}—{% endif %}</td>
|
||
{% else %}
|
||
<td class="text-nowrap text-end" data-order="{{ m.best_reps }}">{{ m.best_reps }}</td>
|
||
<td class="text-nowrap text-end" data-order="{{ m.total_reps }}">{{ m.total_reps }}</td>
|
||
{% endif %}
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% elif query %}
|
||
<div class="alert alert-warning">
|
||
Упражнение «{{ query }}» не найдено.
|
||
{% if suggestions %}Возможно, вы искали:{% endif %}
|
||
</div>
|
||
{% if suggestions %}
|
||
<div class="list-group">
|
||
{% for item in suggestions %}
|
||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
|
||
href="{{ url_for('exercises').include_query_params(name=item.name) }}">
|
||
{{ item.name }}
|
||
<span class="badge text-bg-secondary rounded-pill">{{ item.count }}</span>
|
||
</a>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
{% elif not exercise_names %}
|
||
<div class="alert alert-info">
|
||
В базе пока нет тренировок. Загрузи их из заметок командой:
|
||
<br><code>docker compose exec fastapi-app python -m app.cli import</code>
|
||
</div>
|
||
{% endif %}
|
||
{% endblock %}
|
||
|
||
{% block extra_scripts %}
|
||
{% if chart %}<script src="{{ url_for('static', path='/js/chart.umd.js') }}"></script>{% endif %}
|
||
{% endblock %}
|