massive frontend refactoring (works good)
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import { state, saveState, genId } from '../state.js';
|
||||
import { renderMainScreen } from '../render/main-screen.js';
|
||||
import { showCardioDialog } from '../modals/cardio-modal.js';
|
||||
|
||||
export function addCardio(workoutId) {
|
||||
showCardioDialog(null, (cardio) => {
|
||||
if (!cardio) return;
|
||||
const w = state.workouts.find(x => x.id === workoutId);
|
||||
cardio.id = genId();
|
||||
w.cardio.push(cardio);
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
});
|
||||
}
|
||||
|
||||
export function editCardio(workoutId, cardioId) {
|
||||
const w = state.workouts.find(x => x.id === workoutId);
|
||||
const cardio = w.cardio.find(c => c.id === cardioId);
|
||||
showCardioDialog(cardio, (newData) => {
|
||||
if (!newData) return;
|
||||
Object.assign(cardio, newData);
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteCardio(workoutId, cardioId) {
|
||||
if (!confirm('Удалить кардио?')) return;
|
||||
const w = state.workouts.find(x => x.id === workoutId);
|
||||
w.cardio = w.cardio.filter(c => c.id !== cardioId);
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
}
|
||||
|
||||
window.CL = { addCardio, editCardio, deleteCardio };
|
||||
@@ -0,0 +1,62 @@
|
||||
import { state, saveState, genId } from '../state.js';
|
||||
import { renderMainScreen } from '../render/main-screen.js';
|
||||
import { showExerciseDialog } from '../modals/exercise-modal.js';
|
||||
|
||||
export function addExercise(workoutId) {
|
||||
const w = state.workouts.find(x => x.id === workoutId);
|
||||
showExerciseDialog(w.type, (name) => {
|
||||
if (!name) return;
|
||||
const ex = { id: genId(), name, sets: [] };
|
||||
w.exercises.push(ex);
|
||||
addToRef(name, w.type);
|
||||
state.expandedExerciseId = ex.id;
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
});
|
||||
}
|
||||
|
||||
export function addToRef(name, workoutType = '') {
|
||||
const normalizedName = name.trim().toLowerCase();
|
||||
const exists = state.exerciseRef.find(
|
||||
e => e.name.trim().toLowerCase() === normalizedName
|
||||
);
|
||||
if (!exists) {
|
||||
state.exerciseRef.push({
|
||||
id: genId(),
|
||||
name: name.trim(),
|
||||
workoutType,
|
||||
});
|
||||
} else if (!exists.workoutType && workoutType) {
|
||||
exists.workoutType = workoutType;
|
||||
}
|
||||
}
|
||||
|
||||
export function toggleExercise(exerciseId) {
|
||||
state.expandedExerciseId =
|
||||
state.expandedExerciseId === exerciseId ? null : exerciseId;
|
||||
renderMainScreen();
|
||||
}
|
||||
|
||||
export function deleteExercise(workoutId, exerciseId) {
|
||||
if (!confirm('Удалить упражнение?')) return;
|
||||
const w = state.workouts.find(x => x.id === workoutId);
|
||||
w.exercises = w.exercises.filter(e => e.id !== exerciseId);
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
}
|
||||
|
||||
export function getExerciseHistory(exerciseName) {
|
||||
const normalizedName = exerciseName.trim().toLowerCase();
|
||||
const history = [];
|
||||
for (const w of state.workouts) {
|
||||
for (const ex of w.exercises) {
|
||||
if (ex.name.trim().toLowerCase() === normalizedName && ex.sets.length > 0) {
|
||||
history.push({ date: w.startTime, sets: ex.sets });
|
||||
}
|
||||
}
|
||||
}
|
||||
history.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
return history.slice(0, 3);
|
||||
}
|
||||
|
||||
window.EL = { addExercise, toggleExercise, deleteExercise };
|
||||
@@ -0,0 +1,47 @@
|
||||
import { state, saveState, genId } from '../state.js';
|
||||
import { renderMainScreen } from '../render/main-screen.js';
|
||||
import { showBodyMetricsDialog } from '../modals/metrics-modal.js';
|
||||
|
||||
export function showBodyMetrics() {
|
||||
showBodyMetricsDialog(state.bodyMetrics, (newMetric) => {
|
||||
if (!newMetric) return;
|
||||
newMetric.id = genId();
|
||||
state.bodyMetrics.unshift(newMetric);
|
||||
state.bodyMetrics.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
alert('Замеры сохранены!');
|
||||
});
|
||||
}
|
||||
|
||||
export function editBodyMetric(metricId) {
|
||||
const metric = state.bodyMetrics.find(m => m.id === metricId);
|
||||
if (!metric) return;
|
||||
showBodyMetricsDialog(state.bodyMetrics, (updatedData) => {
|
||||
if (!updatedData) return;
|
||||
Object.assign(metric, updatedData);
|
||||
state.bodyMetrics.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
saveState();
|
||||
// Если сейчас на экране настроек — перерисуем список
|
||||
const list = document.getElementById('body-metrics-list');
|
||||
if (list && typeof window.SettingsLogic !== 'undefined') {
|
||||
window.SettingsLogic._renderBodyMetricsList();
|
||||
} else {
|
||||
renderMainScreen();
|
||||
}
|
||||
}, metric);
|
||||
}
|
||||
|
||||
export function deleteMetric(id) {
|
||||
if (!confirm('Удалить замер?')) return;
|
||||
state.bodyMetrics = state.bodyMetrics.filter(m => m.id !== id);
|
||||
saveState();
|
||||
const list = document.getElementById('body-metrics-list');
|
||||
if (list && typeof window.SettingsLogic !== 'undefined') {
|
||||
window.SettingsLogic._renderBodyMetricsList();
|
||||
} else {
|
||||
renderMainScreen();
|
||||
}
|
||||
}
|
||||
|
||||
window.ML = { showBodyMetrics, editBodyMetric, deleteMetric };
|
||||
@@ -0,0 +1,45 @@
|
||||
import { state, saveState, genId } from '../state.js';
|
||||
import { renderMainScreen } from '../render/main-screen.js';
|
||||
import { showSetDialog } from '../modals/set-modal.js';
|
||||
import { getExerciseHistory } from './exercise.js';
|
||||
|
||||
export function addSet(workoutId, exerciseId) {
|
||||
const w = state.workouts.find(x => x.id === workoutId);
|
||||
const ex = w.exercises.find(e => e.id === exerciseId);
|
||||
const lastSet = ex.sets[ex.sets.length - 1];
|
||||
const history = getExerciseHistory(ex.name);
|
||||
|
||||
showSetDialog(lastSet, history, (setData) => {
|
||||
if (!setData) return;
|
||||
setData.id = genId();
|
||||
setData.timestamp = new Date().toISOString();
|
||||
ex.sets.push(setData);
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
});
|
||||
}
|
||||
|
||||
export function editSet(workoutId, exerciseId, setId) {
|
||||
const w = state.workouts.find(x => x.id === workoutId);
|
||||
const ex = w.exercises.find(e => e.id === exerciseId);
|
||||
const set = ex.sets.find(s => s.id === setId);
|
||||
const history = getExerciseHistory(ex.name);
|
||||
|
||||
showSetDialog(set, history, (setData) => {
|
||||
if (!setData) return;
|
||||
Object.assign(set, setData);
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteSet(workoutId, exerciseId, setId) {
|
||||
if (!confirm('Удалить подход?')) return;
|
||||
const w = state.workouts.find(x => x.id === workoutId);
|
||||
const ex = w.exercises.find(e => e.id === exerciseId);
|
||||
ex.sets = ex.sets.filter(s => s.id !== setId);
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
}
|
||||
|
||||
window.SL = { addSet, editSet, deleteSet };
|
||||
@@ -0,0 +1,185 @@
|
||||
import { state, saveState } from '../state.js';
|
||||
import { showEditExerciseDialog } from '../modals/edit-exercise-modal.js';
|
||||
|
||||
export function showSettings() {
|
||||
const app = document.getElementById('app');
|
||||
app.innerHTML = '';
|
||||
|
||||
const screen = document.createElement('div');
|
||||
screen.className = 'settings-screen';
|
||||
screen.innerHTML = `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||
<h2>⚙️ Настройки</h2>
|
||||
<button class="btn btn-small btn-secondary" onclick="App.backToMain()">← Назад</button>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>📋 Справочник упражнений</h3>
|
||||
<p class="text-muted text-small mb-10">Управляйте списком упражнений и привязывайте их к типам тренировок.</p>
|
||||
<div class="field-group">
|
||||
<div style="display:flex; gap:6px;">
|
||||
<input type="text" id="new-ref-exercise" placeholder="Новое упражнение...">
|
||||
<select id="new-ref-type" style="width: auto; min-width: 120px;">
|
||||
<option value="">тип...</option>
|
||||
<option value="грудь-руки">грудь-руки</option>
|
||||
<option value="ноги">ноги</option>
|
||||
<option value="спина-плечи">спина-плечи</option>
|
||||
<option value="круговая">круговая</option>
|
||||
<option value="силовая">силовая</option>
|
||||
<option value="многоповторная">многоповторная</option>
|
||||
</select>
|
||||
<button class="btn btn-small" style="width:auto; padding: 0 20px;" onclick="SettingsLogic.addRefExercise()">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ref-exercises-list"></div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>📏 Замеры тела</h3>
|
||||
<div id="body-metrics-list"></div>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>🗑 Данные</h3>
|
||||
<button class="btn btn-danger" onclick="SettingsLogic.clearAllData()">Очистить все данные</button>
|
||||
</div>
|
||||
`;
|
||||
app.appendChild(screen);
|
||||
_renderRefExercises();
|
||||
_renderBodyMetricsList();
|
||||
}
|
||||
|
||||
export function addRefExercise() {
|
||||
const input = document.getElementById('new-ref-exercise');
|
||||
const typeSelect = document.getElementById('new-ref-type');
|
||||
const name = input.value.trim();
|
||||
if (!name) return;
|
||||
const normalizedName = name.trim().toLowerCase();
|
||||
const exists = state.exerciseRef.find(
|
||||
e => e.name.trim().toLowerCase() === normalizedName
|
||||
);
|
||||
if (!exists) {
|
||||
state.exerciseRef.push({
|
||||
id: (state.nextId++).toString(),
|
||||
name: name.trim(),
|
||||
workoutType: typeSelect.value,
|
||||
});
|
||||
}
|
||||
saveState();
|
||||
input.value = '';
|
||||
typeSelect.value = '';
|
||||
_renderRefExercises();
|
||||
}
|
||||
|
||||
export function deleteRefExercise(id) {
|
||||
if (!confirm('Удалить из справочника? (в тренировках останется)')) return;
|
||||
state.exerciseRef = state.exerciseRef.filter(e => e.id !== id);
|
||||
saveState();
|
||||
_renderRefExercises();
|
||||
}
|
||||
|
||||
export function updateRefExerciseType(id, newType) {
|
||||
const ex = state.exerciseRef.find(e => e.id === id);
|
||||
if (ex) {
|
||||
ex.workoutType = newType;
|
||||
saveState();
|
||||
}
|
||||
}
|
||||
|
||||
export function editRefExercise(id) {
|
||||
const ex = state.exerciseRef.find(e => e.id === id);
|
||||
if (!ex) return;
|
||||
showEditExerciseDialog(ex, (updatedData) => {
|
||||
if (!updatedData) return;
|
||||
Object.assign(ex, updatedData);
|
||||
saveState();
|
||||
_renderRefExercises();
|
||||
});
|
||||
}
|
||||
|
||||
export function clearAllData() {
|
||||
if (!confirm('Удалить ВСЕ данные? Это действие необратимо!')) return;
|
||||
if (!confirm('Точно уверены?')) return;
|
||||
localStorage.clear();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
export function _renderRefExercises() {
|
||||
const list = document.getElementById('ref-exercises-list');
|
||||
if (!list) return;
|
||||
if (state.exerciseRef.length === 0) {
|
||||
list.innerHTML = '<p class="text-muted text-small">Справочник пуст</p>';
|
||||
return;
|
||||
}
|
||||
const sorted = [...state.exerciseRef].sort((a, b) => {
|
||||
if (a.workoutType !== b.workoutType) return (a.workoutType || '').localeCompare(b.workoutType || '');
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
list.innerHTML = sorted.map(ex => {
|
||||
let count = 0;
|
||||
state.workouts.forEach(w => {
|
||||
w.exercises.forEach(e => {
|
||||
if (e.name.trim().toLowerCase() === ex.name.trim().toLowerCase()) count++;
|
||||
});
|
||||
});
|
||||
return `
|
||||
<div class="exercise-ref-item">
|
||||
<div style="flex:1;">
|
||||
<span class="name">${ex.name}</span>
|
||||
<span class="count">(${count}×)</span>
|
||||
${ex.workoutType ? `<span class="type-badge">${ex.workoutType}</span>` : ''}
|
||||
</div>
|
||||
<div style="display:flex; gap:4px; align-items:center;">
|
||||
<select onchange="SettingsLogic.updateRefExerciseType('${ex.id}', this.value)"
|
||||
style="width:auto; min-width:100px; padding:6px; font-size:0.8rem;">
|
||||
<option value="">тип...</option>
|
||||
<option value="грудь-руки" ${ex.workoutType==='грудь-руки'?'selected':''}>грудь-руки</option>
|
||||
<option value="ноги" ${ex.workoutType==='ноги'?'selected':''}>ноги</option>
|
||||
<option value="спина-плечи" ${ex.workoutType==='спина-плечи'?'selected':''}>спина-плечи</option>
|
||||
<option value="круговая" ${ex.workoutType==='круговая'?'selected':''}>круговая</option>
|
||||
<option value="силовая" ${ex.workoutType==='силовая'?'selected':''}>силовая</option>
|
||||
<option value="многоповторная" ${ex.workoutType==='многоповторная'?'selected':''}>многоповторная</option>
|
||||
</select>
|
||||
<button class="icon-btn" onclick="SettingsLogic.editRefExercise('${ex.id}')">✎</button>
|
||||
<button class="icon-btn danger" onclick="SettingsLogic.deleteRefExercise('${ex.id}')">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
export function _renderBodyMetricsList() {
|
||||
const list = document.getElementById('body-metrics-list');
|
||||
if (!list) return;
|
||||
if (state.bodyMetrics.length === 0) {
|
||||
list.innerHTML = '<p class="text-muted text-small">Нет замеров</p>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = state.bodyMetrics.map(m => {
|
||||
const date = new Date(m.date);
|
||||
const dateStr = date.toLocaleDateString('ru-RU') + ' ' + date.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });
|
||||
const parts = [];
|
||||
if (m.weight) parts.push(`⚖️ ${m.weight} кг`);
|
||||
if (m.chest) parts.push(`📏 ${m.chest} см`);
|
||||
if (m.waist) parts.push(`📏 ${m.waist} см`);
|
||||
if (m.hips) parts.push(`📏 ${m.hips} см`);
|
||||
if (m.arms) parts.push(`💪 ${m.arms} см`);
|
||||
if (m.legs) parts.push(`🦵 ${m.legs} см`);
|
||||
return `
|
||||
<div class="exercise-ref-item">
|
||||
<div>
|
||||
<div style="font-weight:500;">${dateStr}</div>
|
||||
<div class="text-small text-muted">${parts.join(' • ') || '—'}</div>
|
||||
</div>
|
||||
<div style="display:flex; gap:4px;">
|
||||
<button class="icon-btn" onclick="ML.editBodyMetric('${m.id}')">✎</button>
|
||||
<button class="icon-btn danger" onclick="ML.deleteMetric('${m.id}')">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
window.SettingsLogic = {
|
||||
show: showSettings,
|
||||
addRefExercise, deleteRefExercise, updateRefExerciseType,
|
||||
editRefExercise, clearAllData,
|
||||
_renderRefExercises, _renderBodyMetricsList,
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
import { state, saveState, genId } from '../state.js';
|
||||
import { renderMainScreen } from '../render/main-screen.js';
|
||||
import { showEditWorkoutDateDialog } from '../modals/edit-workout-date-modal.js';
|
||||
|
||||
export function startWorkout() {
|
||||
const w = {
|
||||
id: genId(),
|
||||
startTime: new Date().toISOString(),
|
||||
endTime: null,
|
||||
type: '',
|
||||
exercises: [],
|
||||
cardio: [],
|
||||
notes: '',
|
||||
synced: false,
|
||||
};
|
||||
state.workouts.unshift(w);
|
||||
state.expandedWorkoutId = w.id;
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
}
|
||||
|
||||
export function finishWorkout(workoutId) {
|
||||
if (!confirm('Завершить тренировку?')) return;
|
||||
const w = getWorkout(workoutId);
|
||||
w.endTime = new Date().toISOString();
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
}
|
||||
|
||||
export function deleteWorkout(workoutId) {
|
||||
if (!confirm('Удалить тренировку полностью?')) return;
|
||||
state.workouts = state.workouts.filter(w => w.id !== workoutId);
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
}
|
||||
|
||||
export function toggleWorkout(workoutId) {
|
||||
state.expandedWorkoutId =
|
||||
state.expandedWorkoutId === workoutId ? null : workoutId;
|
||||
renderMainScreen();
|
||||
}
|
||||
|
||||
export function getWorkout(id) {
|
||||
return state.workouts.find(w => w.id === id);
|
||||
}
|
||||
|
||||
export function getActiveWorkout() {
|
||||
return state.workouts.find(w => !w.endTime);
|
||||
}
|
||||
|
||||
export function editWorkoutDate(workoutId) {
|
||||
const w = getWorkout(workoutId);
|
||||
if (!w) return;
|
||||
showEditWorkoutDateDialog(w, (newDates) => {
|
||||
if (!newDates) return;
|
||||
w.startTime = newDates.startTime;
|
||||
w.endTime = newDates.endTime;
|
||||
saveState();
|
||||
renderMainScreen();
|
||||
});
|
||||
}
|
||||
|
||||
window.WL = {
|
||||
startWorkout, finishWorkout, deleteWorkout,
|
||||
toggleWorkout, editWorkoutDate,
|
||||
};
|
||||
Reference in New Issue
Block a user