const Modal = {
show(html) {
const container = document.getElementById('modal-container');
container.innerHTML = `
${isEdit ? 'Редактировать' : 'Новый'} подход
${historyHtml}
${this.stepperHtml('m-set-weight', existingSet?.weight ?? '', 0.5, 'Вес', 'decimal')}
${this.stepperHtml('m-set-reps', existingSet?.reps ?? '', 1, 'Повт.', 'numeric')}
${[1,2,3,4,5].map(r => `
`).join('')}
`);
this._setData = {
isDropset: isDropset,
weight: existingSet?.weight || '',
reps: existingSet?.reps || '',
rating: existingSet?.rating || 3,
subSets: existingSet?.subSets ? JSON.parse(JSON.stringify(existingSet.subSets)) : [],
};
this._setCb = callback;
this._stepperChangeHook = null;
if (isDropset) {
if (this._setData.subSets.length === 0) {
this._addSubset();
} else {
this._renderSubsets();
}
}
this._selectRating(this._setData.rating);
},
_toggleDropsetMode() {
const isDropset = document.getElementById('m-set-dropset').checked;
this._setData.isDropset = isDropset;
document.getElementById('m-set-normal').classList.toggle('hidden', isDropset);
document.getElementById('m-set-dropset-block').classList.toggle('hidden', !isDropset);
if (isDropset && this._setData.subSets.length === 0) {
this._addSubset();
} else if (isDropset) {
this._renderSubsets();
}
},
_addSubset() {
const last = this._setData.subSets[this._setData.subSets.length - 1];
this._setData.subSets.push({
weight: last ? last.weight : '',
reps: last ? last.reps : '',
});
this._renderSubsets();
},
_renderSubsets() {
const list = document.getElementById('m-subsets-list');
list.innerHTML = this._setData.subSets.map((s, i) => `
${this.stepperHtml(`m-sub-w-${i}`, s.weight, 0.5, 'Вес', 'decimal').replace(/id="m-sub-w-[^"]+"/, `id="m-sub-w-${i}"`)}
${this.stepperHtml(`m-sub-r-${i}`, s.reps, 1, 'Повт.', 'numeric').replace(/id="m-sub-r-[^"]+"/, `id="m-sub-r-${i}"`)}
`).join('');
// Включаем хук для синхронизации с _setData
this._stepperChangeHook = (id) => {
const wMatch = id.match(/^m-sub-w-(\d+)$/);
const rMatch = id.match(/^m-sub-r-(\d+)$/);
if (wMatch) {
const i = parseInt(wMatch[1]);
this._setData.subSets[i].weight = document.getElementById(id).value;
} else if (rMatch) {
const i = parseInt(rMatch[1]);
this._setData.subSets[i].reps = document.getElementById(id).value;
}
};
},
_removeSubset(i) {
this._setData.subSets.splice(i, 1);
this._renderSubsets();
},
_selectRating(r) {
this._setData.rating = r;
document.querySelectorAll('#m-rating-grid .rating-btn').forEach(btn => {
btn.classList.toggle('active', parseInt(btn.dataset.rating) === r);
});
},
_submitSet() {
if (this._setData.isDropset) {
// Синхронизируем значения из input в _setData перед сабмитом
this._setData.subSets.forEach((s, i) => {
const wInput = document.getElementById(`m-sub-w-${i}`);
const rInput = document.getElementById(`m-sub-r-${i}`);
if (wInput) s.weight = wInput.value;
if (rInput) s.reps = rInput.value;
});
if (this._setData.subSets.length === 0) {
alert('Добавьте хотя бы одно сбрасывание');
return;
}
const result = {
isDropset: true,
subSets: this._setData.subSets.map(s => ({
weight: parseFloat(s.weight) || 0,
reps: parseInt(s.reps) || 0,
})),
rating: this._setData.rating,
};
this.close();
this._setCb(result);
} else {
const weight = parseFloat(document.getElementById('m-set-weight').value);
const reps = parseInt(document.getElementById('m-set-reps').value);
if (!weight && !reps) {
alert('Укажите вес и повторения');
return;
}
const result = {
isDropset: false,
weight: weight || 0,
reps: reps || 0,
rating: this._setData.rating,
};
this.close();
this._setCb(result);
}
},
// ============================================================
// Dialog: Кардио (со степперами)
// ============================================================
showCardioDialog(existing, callback) {
const isEdit = !!existing;
this.show(`
${isEdit ? 'Редактировать' : 'Новое'} кардио
${this.stepperHtml('m-cardio-duration', existing?.duration ?? '', 1, 'мин', 'numeric')}
${this.stepperHtml('m-cardio-distance', existing?.distance ?? '', 0.1, 'км', 'decimal')}
${this.stepperHtml('m-cardio-speed', existing?.speed ?? '', 0.5, 'км/ч', 'decimal')}
${this.stepperHtml('m-cardio-incline', existing?.incline ?? '', 0.5, '—', 'decimal')}
${this.stepperHtml('m-cardio-resistance', existing?.resistance ?? '', 1, '—', 'numeric')}
`);
this._cardioCb = callback;
},
_submitCardio() {
const result = {
type: document.getElementById('m-cardio-type').value,
purpose: document.getElementById('m-cardio-purpose').value,
duration: parseInt(document.getElementById('m-cardio-duration').value) || 0,
distance: parseFloat(document.getElementById('m-cardio-distance').value) || 0,
speed: parseFloat(document.getElementById('m-cardio-speed').value) || 0,
incline: parseFloat(document.getElementById('m-cardio-incline').value) || 0,
resistance: parseInt(document.getElementById('m-cardio-resistance').value) || 0,
notes: document.getElementById('m-cardio-notes').value.trim(),
};
this.close();
this._cardioCb(result);
},
// ============================================================
// Dialog: Замеры тела (со степперами + дата)
// ============================================================
showBodyMetricsDialog(existingMetrics, callback, editingMetric = null) {
const source = editingMetric || existingMetrics[0];
const isEdit = !!editingMetric;
// Форматируем дату для datetime-local
let dateValue = '';
if (source?.date) {
const d = new Date(source.date);
// Формат YYYY-MM-DDTHH:MM
const pad = n => String(n).padStart(2, '0');
dateValue = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
} else if (!isEdit) {
// По умолчанию — сейчас
const d = new Date();
const pad = n => String(n).padStart(2, '0');
dateValue = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
this.show(`
📏 Замеры тела ${isEdit ? '(редактирование)' : ''}
${this.stepperHtml('m-metric-weight', source?.weight ?? '', 0.1, 'кг', 'decimal')}
${this.stepperHtml('m-metric-chest', source?.chest ?? '', 1, 'см', 'numeric')}
${this.stepperHtml('m-metric-waist', source?.waist ?? '', 1, 'см', 'numeric')}
${this.stepperHtml('m-metric-hips', source?.hips ?? '', 1, 'см', 'numeric')}
${this.stepperHtml('m-metric-arms', source?.arms ?? '', 0.5, 'см', 'decimal')}
${this.stepperHtml('m-metric-legs', source?.legs ?? '', 0.5, 'см', 'decimal')}
`);
this._metricsCb = callback;
},
_submitMetrics(isEdit) {
const dateStr = document.getElementById('m-metric-date').value;
const result = {
date: dateStr ? new Date(dateStr).toISOString() : new Date().toISOString(),
weight: parseFloat(document.getElementById('m-metric-weight').value) || null,
chest: parseInt(document.getElementById('m-metric-chest').value) || null,
waist: parseInt(document.getElementById('m-metric-waist').value) || null,
hips: parseInt(document.getElementById('m-metric-hips').value) || null,
arms: parseFloat(document.getElementById('m-metric-arms').value) || null,
legs: parseFloat(document.getElementById('m-metric-legs').value) || null,
};
if (!Object.values(result).some((v, i) => i > 0 && v !== null)) {
alert('Заполните хотя бы одно поле');
return;
}
this.close();
this._metricsCb(result, isEdit);
},
// ============================================================
// Dialog: Редактирование упражнения из справочника
// ============================================================
showEditExerciseDialog(exercise, callback) {
this.show(`
Редактировать упражнение
`);
this._editExerciseCb = callback;
},
_submitEditExercise() {
const name = document.getElementById('m-edit-ex-name').value.trim();
const type = document.getElementById('m-edit-ex-type').value;
if (!name) {
alert('Название не может быть пустым');
return;
}
this.close();
this._editExerciseCb({ name, workoutType: type });
},
// ============================================================
// Dialog: Редактирование даты тренировки
// ============================================================
showEditWorkoutDateDialog(workout, callback) {
const start = new Date(workout.startTime);
const pad = n => String(n).padStart(2, '0');
const startValue = `${start.getFullYear()}-${pad(start.getMonth()+1)}-${pad(start.getDate())}T${pad(start.getHours())}:${pad(start.getMinutes())}`;
let endValue = '';
if (workout.endTime) {
const end = new Date(workout.endTime);
endValue = `${end.getFullYear()}-${pad(end.getMonth()+1)}-${pad(end.getDate())}T${pad(end.getHours())}:${pad(end.getMinutes())}`;
}
this.show(`