// Form for editing one training: date, trainee, trainer, exercises and their approaches import { alert, api, h, SOLO, userName } from './common.js'; const jsonRequest = (method, body) => ({ method, headers: { 'Content-Type': 'application/json' }, body: body === undefined ? undefined : JSON.stringify(body), }); function field(label, input, className = '') { return h('div', { class: className }, h('label', { class: 'form-label small text-muted mb-1' }, label), input); } function approachRow(approach) { const row = h('div', { class: 'row g-2 align-items-end approach-row mb-2' }, h('div', { class: 'col-5 col-md-3' }, h('input', { type: 'number', class: 'form-control form-control-sm', name: 'weight', min: 0, step: 'any', required: true, value: approach.weight, 'aria-label': 'Вес, кг' })), h('div', { class: 'col-auto pb-1' }, '×'), h('div', { class: 'col-4 col-md-2' }, h('input', { type: 'number', class: 'form-control form-control-sm', name: 'reps', min: 1, step: 1, required: true, value: approach.reps, 'aria-label': 'Повторения' })), h('div', { class: 'col-auto' }, h('button', { type: 'button', class: 'btn btn-sm btn-outline-danger', title: 'Убрать подход' }, '×'))); row.querySelector('button').addEventListener('click', () => row.remove()); return row; } function exerciseCard(exercise) { const approaches = h('div', {}, ...exercise.approaches.map(approachRow)); const addApproach = h('button', { type: 'button', class: 'btn btn-sm btn-outline-secondary' }, '+ подход'); addApproach.addEventListener('click', () => { // a new approach starts as a copy of the last one: sets usually repeat const last = approaches.lastElementChild; approaches.append(approachRow({ weight: last ? last.querySelector('[name=weight]').value : 0, reps: last ? last.querySelector('[name=reps]').value : 10, })); }); const remove = h('button', { type: 'button', class: 'btn btn-sm btn-outline-danger', title: 'Убрать упражнение' }, 'Убрать упражнение'); const card = h('div', { class: 'card exercise-card' }, h('div', { class: 'card-body' }, h('div', { class: 'row g-2 align-items-end mb-3' }, field('Упражнение', h('input', { type: 'text', class: 'form-control', name: 'name', list: 'edit-exercise-names', maxlength: 255, required: true, value: exercise.name }), 'col-12 col-md-6'), h('div', { class: 'col-auto form-check ms-2 mb-2' }, h('input', { type: 'checkbox', class: 'form-check-input', name: 'splitted_weight', id: `per-arm-${Math.random().toString(36).slice(2)}`, checked: exercise.splitted_weight }), h('label', { class: 'form-check-label' }, 'Вес на каждую руку')), h('div', { class: 'col-auto ms-md-auto' }, remove)), h('div', { class: 'small text-muted mb-1' }, 'Подходы: вес, кг (0 — без веса) × повторения'), approaches, addApproach)); // the checkbox label must point to its checkbox const checkbox = card.querySelector('[name=splitted_weight]'); card.querySelector('.form-check-label').setAttribute('for', checkbox.id); remove.addEventListener('click', () => card.remove()); return card; } function readForm(form) { return { date: form.elements.date.value, trainee_id: Number(form.elements.trainee_id.value), trainer_id: form.elements.trainer_id.value ? Number(form.elements.trainer_id.value) : null, exercises: [...form.querySelectorAll('.exercise-card')].map((card) => ({ name: card.querySelector('[name=name]').value, splitted_weight: card.querySelector('[name=splitted_weight]').checked, approaches: [...card.querySelectorAll('.approach-row')].map((row) => ({ weight: Number(row.querySelector('[name=weight]').value), reps: Number(row.querySelector('[name=reps]').value), })), })), }; } function userSelect(name, users, selectedId, emptyTitle) { return h('select', { class: 'form-select', name, required: !emptyTitle }, emptyTitle ? h('option', { value: '', selected: selectedId === null }, emptyTitle) : null, ...users.map((user) => h('option', { value: user.id, selected: user.id === selectedId }, userName(user)))); } /** * trainees / trainers: users to choose from (trainees is null when the trainee can't be changed). onDone() is called after the training was saved * or deleted, onCancel() on "Отмена". */ export function editForm(training, exerciseNames, { trainees, trainers }, { onDone, onCancel }) { const exercisesBox = h('div', {}, ...training.exercises.map(exerciseCard)); const addExercise = h('button', { type: 'button', class: 'btn btn-outline-secondary mb-3' }, '+ упражнение'); addExercise.addEventListener('click', () => { const card = exerciseCard({ name: '', splitted_weight: false, approaches: [{ weight: 0, reps: 10 }] }); exercisesBox.append(card); card.querySelector('[name=name]').focus(); }); const status = h('div', {}); const save = h('button', { type: 'submit', class: 'btn btn-primary' }, 'Сохранить'); const cancel = h('button', { type: 'button', class: 'btn btn-outline-secondary' }, 'Отмена'); cancel.addEventListener('click', onCancel); const remove = h('button', { type: 'button', class: 'btn btn-outline-danger ms-auto' }, 'Удалить тренировку'); const form = h('form', {}, h('datalist', { id: 'edit-exercise-names' }, ...exerciseNames.map((item) => h('option', { value: item.name }))), h('div', { class: 'row g-3 mb-3' }, field('Дата', h('input', { type: 'date', class: 'form-control', name: 'date', required: true, value: training.date }), 'col-12 col-md-3'), trainees ? field('Тренирующийся', userSelect('trainee_id', trainees, training.trainee_id), 'col-12 col-md-3') : h('input', { type: 'hidden', name: 'trainee_id', value: training.trainee_id }), field('Тренер', userSelect('trainer_id', trainers, training.trainer_id, `${SOLO} (без тренера)`), 'col-12 col-md-3')), exercisesBox, addExercise, h('div', { class: 'd-flex gap-2 align-items-center' }, save, cancel, remove), status); form.addEventListener('submit', async (event) => { event.preventDefault(); save.disabled = true; try { await api(`/trainings/${training.id}/`, jsonRequest('PUT', readForm(form))); onDone(); } catch (error) { status.replaceChildren(alert('danger', `Не удалось сохранить: ${error.message}`)); save.disabled = false; } }); remove.addEventListener('click', async () => { if (!confirm('Удалить тренировку вместе с упражнениями и подходами?')) { return; } try { await api(`/trainings/${training.id}/`, jsonRequest('DELETE')); onDone(); } catch (error) { status.replaceChildren(alert('danger', `Не удалось удалить: ${error.message}`)); } }); return form; }