207 lines
9.0 KiB
JavaScript
207 lines
9.0 KiB
JavaScript
import { state } from '../state.js';
|
||
import { renderExercise } from './exercise-card.js';
|
||
import { renderCardioItem } from './cardio-card.js';
|
||
import { formatDate } from '../utils.js';
|
||
|
||
export function renderWorkoutCard(w) {
|
||
const isActive = !w.endTime;
|
||
const isExpanded = state.expandedWorkoutId === w.id;
|
||
const isReadOnly = w.synced && w.endTime;
|
||
|
||
// Определяем цвет тренировки
|
||
let clientColor = null;
|
||
let clientName = null;
|
||
if (w.client_id) {
|
||
const client = state.clients.find(c => c.id == w.client_id);
|
||
if (client) {
|
||
clientColor = client.color;
|
||
clientName = client.full_name;
|
||
}
|
||
}
|
||
|
||
const div = document.createElement('div');
|
||
div.className = 'workout-accordion';
|
||
|
||
// Добавляем цветной border-left, если есть клиент
|
||
if (clientColor) {
|
||
div.style.borderLeft = `4px solid ${clientColor}`;
|
||
}
|
||
|
||
const headerClass = `workout-header ${isActive ? 'active' : 'completed'}`;
|
||
const dateStr = formatDate(w.startTime);
|
||
const typeStr = w.type || 'тип не выбран';
|
||
const exCount = w.exercises.length;
|
||
const cardioCount = w.cardio.length;
|
||
|
||
let statusBadge;
|
||
if (isReadOnly) statusBadge = '<span class="workout-badge synced-badge">✓ архив</span>';
|
||
else if (w.synced) statusBadge = '<span class="workout-badge synced-badge">✓</span>';
|
||
else statusBadge = '<span class="workout-badge dirty">● не сохранено</span>';
|
||
|
||
const editDateBtn = !isReadOnly
|
||
? `<span class="editable-date" onclick="event.stopPropagation(); WL.editWorkoutDate('${w.id}')">
|
||
<span class="edit-icon">✎</span>
|
||
</span>`
|
||
: '';
|
||
|
||
// Метка клиента (если есть)
|
||
const clientLabel = clientName
|
||
? `<div class="workout-meta" style="color:${clientColor}; font-weight:600;">👤 ${clientName}</div>`
|
||
: '';
|
||
|
||
div.innerHTML = `
|
||
<div class="${headerClass}" onclick="WL.toggleWorkout('${w.id}')">
|
||
<div>
|
||
<div style="font-weight:600;">${isActive ? '🔥 Тренировка' : '✅ Завершена'}</div>
|
||
<div class="workout-meta">${dateStr} ${editDateBtn} • ${typeStr}</div>
|
||
${clientLabel}
|
||
<div class="workout-meta">Упр: ${exCount} • Кардио: ${cardioCount}</div>
|
||
</div>
|
||
<div>${statusBadge} <span class="chevron ${isExpanded ? 'open' : ''}">›</span></div>
|
||
</div>
|
||
`;
|
||
|
||
const body = document.createElement('div');
|
||
body.className = `workout-body ${isExpanded ? 'expanded' : ''}`;
|
||
if (isExpanded) {
|
||
body.appendChild(renderWorkoutDetails(w));
|
||
}
|
||
div.appendChild(body);
|
||
return div;
|
||
}
|
||
|
||
export function renderWorkoutDetails(w) {
|
||
const wrap = document.createElement('div');
|
||
const isActive = !w.endTime;
|
||
const isReadOnly = w.synced && w.endTime;
|
||
|
||
if (isReadOnly) {
|
||
const banner = document.createElement('div');
|
||
banner.className = 'read-only-banner';
|
||
banner.textContent = '🔒 Тренировка в архиве — только просмотр';
|
||
wrap.appendChild(banner);
|
||
}
|
||
|
||
// Тип тренировки
|
||
const typeCard = document.createElement('div');
|
||
typeCard.className = 'card';
|
||
if (isReadOnly) {
|
||
typeCard.innerHTML = `
|
||
<div class="field-group" style="margin:0;">
|
||
<span class="text-muted text-small">Тип тренировки</span>
|
||
<div style="font-weight:600; margin-top:4px;">${w.type || '—'}</div>
|
||
</div>`;
|
||
} else {
|
||
typeCard.innerHTML = `
|
||
<label class="field-group" style="margin:0;">
|
||
<span>Тип тренировки</span>
|
||
<select onchange="App.getWorkout('${w.id}').type = this.value; App.saveAndRender();">
|
||
<option value="">— выберите —</option>
|
||
<option ${w.type==='грудь-руки'?'selected':''}>грудь-руки</option>
|
||
<option ${w.type==='ноги'?'selected':''}>ноги</option>
|
||
<option ${w.type==='спина-плечи'?'selected':''}>спина-плечи</option>
|
||
<option ${w.type==='круговая'?'selected':''}>круговая</option>
|
||
<option ${w.type==='силовая'?'selected':''}>силовая</option>
|
||
<option ${w.type==='многоповторная'?'selected':''}>многоповторная</option>
|
||
</select>
|
||
</label>`;
|
||
}
|
||
wrap.appendChild(typeCard);
|
||
|
||
// Кнопки добавления
|
||
if (isActive && !isReadOnly) {
|
||
const addSection = document.createElement('div');
|
||
addSection.className = 'add-section';
|
||
addSection.innerHTML = `
|
||
<button class="btn btn-secondary" onclick="EL.addExercise('${w.id}')">+ Упражнение</button>
|
||
<button class="btn btn-secondary" onclick="CL.addCardio('${w.id}')">+ Кардио</button>
|
||
`;
|
||
wrap.appendChild(addSection);
|
||
}
|
||
|
||
// Кардио: разминка сверху
|
||
const cardioWarmup = w.cardio.filter(c => c.purpose === 'warmup' || c.purpose === 'separate');
|
||
const cardioCooldown = w.cardio.filter(c => c.purpose === 'cooldown');
|
||
|
||
if (cardioWarmup.length > 0) {
|
||
const title = document.createElement('h4');
|
||
title.style.margin = '16px 0 8px';
|
||
title.textContent = '🔥 Разминка / Кардио';
|
||
wrap.appendChild(title);
|
||
cardioWarmup.forEach(c => wrap.appendChild(renderCardioItem(w.id, c, isReadOnly)));
|
||
}
|
||
|
||
// Упражнения
|
||
[...w.exercises].reverse().forEach(ex => {
|
||
wrap.appendChild(renderExercise(w, ex, isReadOnly));
|
||
});
|
||
|
||
// Заминка внизу
|
||
if (cardioCooldown.length > 0) {
|
||
const title = document.createElement('h4');
|
||
title.style.margin = '16px 0 8px';
|
||
title.textContent = '🧘 Заминка';
|
||
wrap.appendChild(title);
|
||
cardioCooldown.forEach(c => wrap.appendChild(renderCardioItem(w.id, c, isReadOnly)));
|
||
}
|
||
|
||
// Комментарий
|
||
const notesCard = document.createElement('div');
|
||
notesCard.className = 'card mt-10';
|
||
if (isReadOnly) {
|
||
notesCard.innerHTML = `
|
||
<div class="field-group" style="margin:0;">
|
||
<span class="text-muted text-small">Комментарий</span>
|
||
<div style="margin-top:4px; white-space:pre-wrap;">${w.notes || '—'}</div>
|
||
</div>`;
|
||
} else {
|
||
notesCard.innerHTML = `
|
||
<label class="field-group" style="margin:0;">
|
||
<span>Комментарий к тренировке</span>
|
||
<textarea placeholder="Как прошла тренировка...">${w.notes || ''}</textarea>
|
||
</label>`;
|
||
notesCard.querySelector('textarea').oninput = (e) => {
|
||
w.notes = e.target.value;
|
||
window.App.saveAndRender();
|
||
};
|
||
}
|
||
wrap.appendChild(notesCard);
|
||
|
||
// Блок завершения
|
||
const finishBlock = document.createElement('div');
|
||
finishBlock.className = 'finish-block';
|
||
|
||
if (isReadOnly) {
|
||
finishBlock.innerHTML = `
|
||
<h3>🔒 Архив</h3>
|
||
<p class="text-muted text-small">Тренировка синхронизирована и завершена. Редактирование недоступно.</p>`;
|
||
} else if (isActive) {
|
||
// Проверяем, есть ли уже данные в тренировке
|
||
const hasData = (w.exercises && w.exercises.length > 0) || (w.cardio && w.cardio.length > 0);
|
||
|
||
// Кнопка отмены показывается ТОЛЬКО если тренировка пустая
|
||
const cancelBtnHtml = !hasData ? `
|
||
<button class="btn btn-ghost mt-10" style="color: var(--danger); border-color: var(--danger);" onclick="WL.cancelEmptyWorkout('${w.id}')">
|
||
✕ Отменить пустую тренировку
|
||
</button>
|
||
` : '';
|
||
|
||
finishBlock.innerHTML = `
|
||
<h3>Завершение тренировки</h3>
|
||
<p class="text-muted text-small">После завершения данные останутся локально до синхронизации</p>
|
||
<div style="display: flex; flex-direction: column; gap: 10px; margin-top: 10px;">
|
||
${cancelBtnHtml}
|
||
<button class="btn btn-danger" onclick="WL.finishWorkout('${w.id}')">⏹ Закончить тренировку</button>
|
||
</div>
|
||
`;
|
||
} else {
|
||
finishBlock.innerHTML = `
|
||
<h3>Тренировка завершена</h3>
|
||
<p class="text-muted text-small">До синхронизации можно редактировать</p>
|
||
<button class="btn btn-danger" onclick="WL.deleteWorkout('${w.id}')">🗑 Удалить</button>`;
|
||
}
|
||
wrap.appendChild(finishBlock);
|
||
|
||
return wrap;
|
||
}
|