603 lines
27 KiB
JavaScript
603 lines
27 KiB
JavaScript
const Modal = {
|
||
show(html) {
|
||
const container = document.getElementById('modal-container');
|
||
container.innerHTML = `<div class="modal-overlay" onclick="Modal.closeOnBg(event)">${html}</div>`;
|
||
},
|
||
|
||
close() {
|
||
document.getElementById('modal-container').innerHTML = '';
|
||
},
|
||
|
||
closeOnBg(e) {
|
||
if (e.target.classList.contains('modal-overlay')) this.close();
|
||
},
|
||
|
||
// ============================================================
|
||
// Универсальный степпер
|
||
// ============================================================
|
||
stepperHtml(id, value, step, placeholder, inputmode = 'decimal') {
|
||
const displayValue = value !== '' && value !== null && value !== undefined ? value : '';
|
||
return `
|
||
<div class="stepper-field">
|
||
<button type="button" class="stepper-btn" onclick="Modal.stepValue('${id}', -${step})">−</button>
|
||
<input type="number" id="${id}" step="${step}" value="${displayValue}"
|
||
placeholder="${placeholder}" inputmode="${inputmode}"
|
||
onchange="Modal._onStepperChange('${id}')">
|
||
<button type="button" class="stepper-btn" onclick="Modal.stepValue('${id}', ${step})">+</button>
|
||
</div>
|
||
`;
|
||
},
|
||
|
||
stepValue(id, delta) {
|
||
const input = document.getElementById(id);
|
||
if (!input) return;
|
||
const current = parseFloat(input.value) || 0;
|
||
const newVal = Math.max(0, current + delta);
|
||
// Округляем до шага, чтобы избежать 0.30000000004
|
||
const step = parseFloat(input.step) || 1;
|
||
const rounded = Math.round(newVal / step) * step;
|
||
input.value = parseFloat(rounded.toFixed(4));
|
||
this._onStepperChange(id);
|
||
},
|
||
|
||
_onStepperChange(id) {
|
||
// Хук для подклассов модалок — переопределяется при необходимости
|
||
if (this._stepperChangeHook) {
|
||
this._stepperChangeHook(id);
|
||
}
|
||
},
|
||
|
||
// ============================================================
|
||
// Dialog: Упражнение
|
||
// ============================================================
|
||
showExerciseDialog(workoutType, callback) {
|
||
const nameCount = {};
|
||
App.state.exerciseRef.forEach(ex => {
|
||
const isMatch = workoutType && ex.workoutType === workoutType;
|
||
const bonus = isMatch ? 100 : 10;
|
||
nameCount[ex.name] = (nameCount[ex.name] || 0) + bonus;
|
||
});
|
||
App.state.workouts.forEach(w => {
|
||
w.exercises.forEach(ex => {
|
||
nameCount[ex.name] = (nameCount[ex.name] || 0) + 1;
|
||
});
|
||
});
|
||
|
||
const sortedNames = Object.keys(nameCount).sort((a, b) => {
|
||
const diff = nameCount[b] - nameCount[a];
|
||
if (diff !== 0) return diff;
|
||
return a.localeCompare(b);
|
||
});
|
||
|
||
this.show(`
|
||
<div class="modal-content">
|
||
<h3 class="modal-title">Добавить упражнение</h3>
|
||
<div class="field-group">
|
||
<label>Название</label>
|
||
<input type="text" id="m-ex-name" placeholder="Начните вводить..."
|
||
oninput="Modal._filterExercises()" autocomplete="off">
|
||
<div id="m-ex-autocomplete" class="autocomplete-list"></div>
|
||
</div>
|
||
<div class="btn-row">
|
||
<button class="btn btn-secondary" onclick="Modal.close()">Отмена</button>
|
||
<button class="btn" onclick="Modal._submitExercise()">Добавить</button>
|
||
</div>
|
||
</div>
|
||
`);
|
||
this._exerciseCb = callback;
|
||
this._exerciseNames = sortedNames;
|
||
setTimeout(() => {
|
||
document.getElementById('m-ex-name').focus();
|
||
this._filterExercises();
|
||
}, 50);
|
||
},
|
||
|
||
_filterExercises() {
|
||
const input = document.getElementById('m-ex-name');
|
||
const list = document.getElementById('m-ex-autocomplete');
|
||
const query = input.value.trim().toLowerCase();
|
||
|
||
let filtered = this._exerciseNames;
|
||
if (query) {
|
||
filtered = filtered.filter(n => n.toLowerCase().includes(query));
|
||
}
|
||
|
||
if (filtered.length === 0) {
|
||
list.innerHTML = '<div class="autocomplete-item text-muted">Нет совпадений — можно ввести новое</div>';
|
||
return;
|
||
}
|
||
|
||
list.innerHTML = filtered.slice(0, 15).map(name => {
|
||
const count = App.state.workouts.reduce((sum, w) =>
|
||
sum + w.exercises.filter(e => e.name === name).length, 0
|
||
);
|
||
return `
|
||
<div class="autocomplete-item" onclick="Modal._selectExercise('${name.replace(/'/g, "\\'")}')">
|
||
${name}
|
||
${count > 0 ? `<span class="freq">${count}×</span>` : ''}
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
},
|
||
|
||
_selectExercise(name) {
|
||
document.getElementById('m-ex-name').value = name;
|
||
document.getElementById('m-ex-autocomplete').innerHTML = '';
|
||
},
|
||
|
||
_submitExercise() {
|
||
const name = document.getElementById('m-ex-name').value.trim();
|
||
this.close();
|
||
this._exerciseCb(name);
|
||
},
|
||
|
||
// ============================================================
|
||
// Dialog: Подход
|
||
// ============================================================
|
||
showSetDialog(existingSet, history, callback) {
|
||
const isEdit = !!existingSet;
|
||
const isDropset = existingSet?.isDropset || false;
|
||
|
||
let historyHtml = '';
|
||
if (history && history.length > 0) {
|
||
const rows = history.map(h => {
|
||
const date = new Date(h.date).toLocaleDateString('ru-RU', {day:'2-digit', month:'2-digit'});
|
||
const setsStr = h.sets.map(s => {
|
||
if (s.isDropset && s.subSets) {
|
||
return s.subSets.map(ss => `${ss.weight}×${ss.reps}`).join('-');
|
||
}
|
||
return `${s.weight}×${s.reps}`;
|
||
}).join(', ');
|
||
return `<div class="history-hint-row">
|
||
<span class="history-hint-date">${date}</span>
|
||
<span class="history-hint-sets">${setsStr}</span>
|
||
</div>`;
|
||
}).join('');
|
||
historyHtml = `
|
||
<div class="history-hint">
|
||
<div class="history-hint-title">📊 Прошлые тренировки</div>
|
||
${rows}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
this.show(`
|
||
<div class="modal-content">
|
||
<h3 class="modal-title">${isEdit ? 'Редактировать' : 'Новый'} подход</h3>
|
||
|
||
${historyHtml}
|
||
|
||
<div class="field-group">
|
||
<label style="display:flex; align-items:center; gap:8px;">
|
||
<input type="checkbox" id="m-set-dropset" ${isDropset ? 'checked' : ''}
|
||
onchange="Modal._toggleDropsetMode()" style="width:auto;">
|
||
Это дроп-сет (несколько сбрасываний веса)
|
||
</label>
|
||
</div>
|
||
|
||
<div id="m-set-normal" class="${isDropset ? 'hidden' : ''}">
|
||
<div class="field-row">
|
||
<div class="field-group">
|
||
<label>Вес (кг)</label>
|
||
${this.stepperHtml('m-set-weight', existingSet?.weight ?? '', 0.5, 'Вес', 'decimal')}
|
||
</div>
|
||
<div class="field-group">
|
||
<label>Повторения</label>
|
||
${this.stepperHtml('m-set-reps', existingSet?.reps ?? '', 1, 'Повт.', 'numeric')}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="m-set-dropset-block" class="${isDropset ? '' : 'hidden'}">
|
||
<div id="m-subsets-list"></div>
|
||
<button class="btn btn-ghost mt-10" style="width:100%;"
|
||
onclick="Modal._addSubset()">+ Добавить сбрасывание</button>
|
||
</div>
|
||
|
||
<div class="field-group">
|
||
<label>Оценка усилия (1 — легко, 5 — отказ)</label>
|
||
<div class="rating-grid" id="m-rating-grid">
|
||
${[1,2,3,4,5].map(r => `
|
||
<button class="rating-btn ${existingSet?.rating === r ? 'active' : ''}"
|
||
data-rating="${r}" onclick="Modal._selectRating(${r})">${r}</button>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="btn-row">
|
||
<button class="btn btn-secondary" onclick="Modal.close()">Отмена</button>
|
||
<button class="btn btn-success" onclick="Modal._submitSet()">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
`);
|
||
|
||
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) => `
|
||
<div class="stepper-row">
|
||
${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}"`)}
|
||
<button class="icon-btn danger" onclick="Modal._removeSubset(${i})">✕</button>
|
||
</div>
|
||
`).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(`
|
||
<div class="modal-content">
|
||
<h3 class="modal-title">${isEdit ? 'Редактировать' : 'Новое'} кардио</h3>
|
||
|
||
<div class="field-row">
|
||
<div class="field-group">
|
||
<label>Тип</label>
|
||
<select id="m-cardio-type">
|
||
<option value="вело" ${existing?.type==='вело'?'selected':''}>🚴 Вело</option>
|
||
<option value="ходьба" ${existing?.type==='ходьба'?'selected':''}>🚶 Ходьба</option>
|
||
<option value="бег" ${existing?.type==='бег'?'selected':''}>🏃 Бег</option>
|
||
<option value="эллипс" ${existing?.type==='эллипс'?'selected':''}>⚡ Эллипс</option>
|
||
<option value="гребля" ${existing?.type==='гребля'?'selected':''}>🚣 Гребля</option>
|
||
</select>
|
||
</div>
|
||
<div class="field-group">
|
||
<label>Назначение</label>
|
||
<select id="m-cardio-purpose">
|
||
<option value="warmup" ${existing?.purpose==='warmup'?'selected':''}>🔥 Разминка</option>
|
||
<option value="cooldown" ${existing?.purpose==='cooldown'?'selected':''}>🧘 Заминка</option>
|
||
<option value="separate" ${existing?.purpose==='separate'?'selected':''}>➕ Отдельная</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field-row-3">
|
||
<div class="field-group">
|
||
<label>Минут</label>
|
||
${this.stepperHtml('m-cardio-duration', existing?.duration ?? '', 1, 'мин', 'numeric')}
|
||
</div>
|
||
<div class="field-group">
|
||
<label>Дистанция</label>
|
||
${this.stepperHtml('m-cardio-distance', existing?.distance ?? '', 0.1, 'км', 'decimal')}
|
||
</div>
|
||
<div class="field-group">
|
||
<label>Скорость</label>
|
||
${this.stepperHtml('m-cardio-speed', existing?.speed ?? '', 0.5, 'км/ч', 'decimal')}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field-row">
|
||
<div class="field-group">
|
||
<label>Наклон</label>
|
||
${this.stepperHtml('m-cardio-incline', existing?.incline ?? '', 0.5, '—', 'decimal')}
|
||
</div>
|
||
<div class="field-group">
|
||
<label>Сопротивление</label>
|
||
${this.stepperHtml('m-cardio-resistance', existing?.resistance ?? '', 1, '—', 'numeric')}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field-group">
|
||
<label>Заметка</label>
|
||
<input type="text" id="m-cardio-notes" value="${existing?.notes || ''}"
|
||
placeholder="Например: тяжёло после 20 минуты">
|
||
</div>
|
||
|
||
<div class="btn-row">
|
||
<button class="btn btn-secondary" onclick="Modal.close()">Отмена</button>
|
||
<button class="btn btn-success" onclick="Modal._submitCardio()">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
`);
|
||
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(`
|
||
<div class="modal-content">
|
||
<h3 class="modal-title">📏 Замеры тела ${isEdit ? '(редактирование)' : ''}</h3>
|
||
|
||
<div class="field-group">
|
||
<label>📅 Дата замера</label>
|
||
<input type="datetime-local" id="m-metric-date" value="${dateValue}">
|
||
</div>
|
||
|
||
<div class="field-row">
|
||
<div class="field-group">
|
||
<label>⚖️ Вес (кг)</label>
|
||
${this.stepperHtml('m-metric-weight', source?.weight ?? '', 0.1, 'кг', 'decimal')}
|
||
</div>
|
||
<div class="field-group">
|
||
<label>📏 Грудь (см)</label>
|
||
${this.stepperHtml('m-metric-chest', source?.chest ?? '', 1, 'см', 'numeric')}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field-row">
|
||
<div class="field-group">
|
||
<label>📏 Талия (см)</label>
|
||
${this.stepperHtml('m-metric-waist', source?.waist ?? '', 1, 'см', 'numeric')}
|
||
</div>
|
||
<div class="field-group">
|
||
<label>📏 Бёдра (см)</label>
|
||
${this.stepperHtml('m-metric-hips', source?.hips ?? '', 1, 'см', 'numeric')}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field-row">
|
||
<div class="field-group">
|
||
<label>💪 Руки (см)</label>
|
||
${this.stepperHtml('m-metric-arms', source?.arms ?? '', 0.5, 'см', 'decimal')}
|
||
</div>
|
||
<div class="field-group">
|
||
<label>🦵 Ноги (см)</label>
|
||
${this.stepperHtml('m-metric-legs', source?.legs ?? '', 0.5, 'см', 'decimal')}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="btn-row">
|
||
<button class="btn btn-secondary" onclick="Modal.close()">Отмена</button>
|
||
<button class="btn btn-success" onclick="Modal._submitMetrics(${isEdit})">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
`);
|
||
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(`
|
||
<div class="modal-content">
|
||
<h3 class="modal-title">Редактировать упражнение</h3>
|
||
<div class="field-group">
|
||
<label>Название</label>
|
||
<input type="text" id="m-edit-ex-name" value="${exercise.name}">
|
||
</div>
|
||
<div class="field-group">
|
||
<label>Тип тренировки</label>
|
||
<select id="m-edit-ex-type">
|
||
<option value="">тип не указан</option>
|
||
<option value="грудь-руки" ${exercise.workoutType==='грудь-руки'?'selected':''}>грудь-руки</option>
|
||
<option value="ноги" ${exercise.workoutType==='ноги'?'selected':''}>ноги</option>
|
||
<option value="спина-плечи" ${exercise.workoutType==='спина-плечи'?'selected':''}>спина-плечи</option>
|
||
<option value="круговая" ${exercise.workoutType==='круговая'?'selected':''}>круговая</option>
|
||
<option value="силовая" ${exercise.workoutType==='силовая'?'selected':''}>силовая</option>
|
||
<option value="многоповторная" ${exercise.workoutType==='многоповторная'?'selected':''}>многоповторная</option>
|
||
</select>
|
||
</div>
|
||
<div class="btn-row">
|
||
<button class="btn btn-secondary" onclick="Modal.close()">Отмена</button>
|
||
<button class="btn btn-success" onclick="Modal._submitEditExercise()">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
`);
|
||
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(`
|
||
<div class="modal-content">
|
||
<h3 class="modal-title">📅 Дата тренировки</h3>
|
||
<div class="field-group">
|
||
<label>Начало</label>
|
||
<input type="datetime-local" id="m-w-start" value="${startValue}">
|
||
</div>
|
||
<div class="field-group">
|
||
<label>Окончание</label>
|
||
<input type="datetime-local" id="m-w-end" value="${endValue}">
|
||
</div>
|
||
<div class="btn-row">
|
||
<button class="btn btn-secondary" onclick="Modal.close()">Отмена</button>
|
||
<button class="btn btn-success" onclick="Modal._submitWorkoutDate()">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
`);
|
||
this._workoutDateCb = callback;
|
||
},
|
||
|
||
_submitWorkoutDate() {
|
||
const startStr = document.getElementById('m-w-start').value;
|
||
const endStr = document.getElementById('m-w-end').value;
|
||
if (!startStr) {
|
||
alert('Укажите дату начала');
|
||
return;
|
||
}
|
||
this.close();
|
||
this._workoutDateCb({
|
||
startTime: new Date(startStr).toISOString(),
|
||
endTime: endStr ? new Date(endStr).toISOString() : null,
|
||
});
|
||
},
|
||
};
|