719 lines
26 KiB
JavaScript
719 lines
26 KiB
JavaScript
// Training Tracker App
|
|
|
|
const app = {
|
|
currentView: 'dashboard',
|
|
exercises: [],
|
|
plans: [],
|
|
sessions: [],
|
|
currentSession: null,
|
|
currentPlan: null,
|
|
exerciseFilter: 'all',
|
|
|
|
// Initialize the app
|
|
async init() {
|
|
this.setupNavigation();
|
|
this.setupForms();
|
|
this.setupTabs();
|
|
await this.loadData();
|
|
this.render();
|
|
},
|
|
|
|
// Setup navigation
|
|
setupNavigation() {
|
|
document.querySelectorAll('.nav-item').forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
const view = item.dataset.view;
|
|
this.navigateTo(view);
|
|
});
|
|
});
|
|
},
|
|
|
|
// Navigate to a view
|
|
navigateTo(viewName) {
|
|
// Hide all views
|
|
document.querySelectorAll('.view').forEach(view => {
|
|
view.classList.remove('active');
|
|
});
|
|
|
|
// Show target view
|
|
const targetView = document.getElementById(`view-${viewName}`);
|
|
if (targetView) {
|
|
targetView.classList.add('active');
|
|
}
|
|
|
|
// Update nav items
|
|
document.querySelectorAll('.nav-item').forEach(item => {
|
|
item.classList.toggle('active', item.dataset.view === viewName);
|
|
});
|
|
|
|
this.currentView = viewName;
|
|
this.render();
|
|
},
|
|
|
|
// Setup form handlers
|
|
setupForms() {
|
|
// Exercise form
|
|
document.getElementById('exercise-form').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
await this.saveExercise();
|
|
});
|
|
|
|
// Plan form
|
|
document.getElementById('plan-form').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
await this.savePlan();
|
|
});
|
|
|
|
// Session form
|
|
document.getElementById('session-form').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
await this.saveSession();
|
|
});
|
|
|
|
// Entry form
|
|
document.getElementById('entry-form').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
await this.saveEntry();
|
|
});
|
|
|
|
// Exercise select change (for entry form)
|
|
document.getElementById('entry-exercise').addEventListener('change', (e) => {
|
|
this.updateEntryFields(e.target.value);
|
|
});
|
|
},
|
|
|
|
// Setup tabs
|
|
setupTabs() {
|
|
document.querySelectorAll('.tab').forEach(tab => {
|
|
tab.addEventListener('click', () => {
|
|
const filter = tab.dataset.filter;
|
|
this.exerciseFilter = filter;
|
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
tab.classList.add('active');
|
|
this.renderExercises();
|
|
});
|
|
});
|
|
},
|
|
|
|
// Load all data
|
|
async loadData() {
|
|
try {
|
|
[this.exercises, this.plans, this.sessions] = await Promise.all([
|
|
api.getExercises(),
|
|
api.getPlans(),
|
|
api.getSessions(),
|
|
]);
|
|
} catch (error) {
|
|
console.error('Failed to load data:', error);
|
|
}
|
|
},
|
|
|
|
// Render current view
|
|
render() {
|
|
switch (this.currentView) {
|
|
case 'dashboard':
|
|
this.renderDashboard();
|
|
break;
|
|
case 'sessions':
|
|
this.renderSessions();
|
|
break;
|
|
case 'exercises':
|
|
this.renderExercises();
|
|
break;
|
|
case 'plans':
|
|
this.renderPlans();
|
|
break;
|
|
}
|
|
},
|
|
|
|
// Render dashboard
|
|
renderDashboard() {
|
|
const container = document.getElementById('recent-sessions');
|
|
const recentSessions = this.sessions.slice(0, 5);
|
|
|
|
if (recentSessions.length === 0) {
|
|
container.innerHTML = '<p class="empty-state">No sessions yet. Start your first workout!</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = recentSessions.map(session => this.renderSessionCard(session)).join('');
|
|
},
|
|
|
|
// Render sessions list
|
|
renderSessions() {
|
|
const container = document.getElementById('sessions-list');
|
|
|
|
if (this.sessions.length === 0) {
|
|
container.innerHTML = '<p class="empty-state">No sessions yet</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = this.sessions.map(session => this.renderSessionCard(session)).join('');
|
|
},
|
|
|
|
// Render session card
|
|
renderSessionCard(session) {
|
|
const date = new Date(session.date).toLocaleDateString('en-US', {
|
|
weekday: 'short',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
});
|
|
const planName = session.plan ? session.plan.name : 'No plan';
|
|
const entryCount = session.entries ? session.entries.length : 0;
|
|
|
|
return `
|
|
<div class="card" onclick="app.viewSession(${session.id})">
|
|
<div class="card-header">
|
|
<div>
|
|
<div class="card-title">${date}</div>
|
|
<div class="card-subtitle">${planName}</div>
|
|
</div>
|
|
<span class="badge badge-strength">${entryCount} exercises</span>
|
|
</div>
|
|
${session.notes ? `<div class="card-body">${session.notes}</div>` : ''}
|
|
</div>
|
|
`;
|
|
},
|
|
|
|
// View session details
|
|
async viewSession(id) {
|
|
try {
|
|
this.currentSession = await api.getSession(id);
|
|
this.renderSessionDetail();
|
|
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
document.getElementById('view-session-detail').classList.add('active');
|
|
} catch (error) {
|
|
alert('Failed to load session');
|
|
}
|
|
},
|
|
|
|
// Render session detail
|
|
renderSessionDetail() {
|
|
const session = this.currentSession;
|
|
if (!session) return;
|
|
|
|
const date = new Date(session.date).toLocaleDateString('en-US', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
});
|
|
|
|
document.getElementById('session-detail-title').textContent = date;
|
|
|
|
let html = '';
|
|
if (session.plan) {
|
|
html += `<p class="card-subtitle">Plan: ${session.plan.name}</p>`;
|
|
}
|
|
if (session.notes) {
|
|
html += `<p class="mt-2">${session.notes}</p>`;
|
|
}
|
|
|
|
html += '<div class="section"><h3>Exercises</h3><div class="card-list">';
|
|
|
|
if (session.entries && session.entries.length > 0) {
|
|
session.entries.forEach(entry => {
|
|
const exercise = entry.exercise || {};
|
|
let details = '';
|
|
|
|
if (exercise.type === 'cardio') {
|
|
const parts = [];
|
|
if (entry.duration) parts.push(`${Math.floor(entry.duration / 60)} min`);
|
|
if (entry.distance) parts.push(`${entry.distance} km`);
|
|
if (entry.heart_rate) parts.push(`${entry.heart_rate} bpm`);
|
|
details = parts.join(' | ');
|
|
} else {
|
|
const parts = [];
|
|
if (entry.weight) parts.push(`${entry.weight} kg`);
|
|
if (entry.sets_completed) parts.push(`${entry.sets_completed} sets`);
|
|
if (entry.reps) parts.push(`${entry.reps} reps`);
|
|
details = parts.join(' x ');
|
|
}
|
|
|
|
html += `
|
|
<div class="session-entry">
|
|
<div class="entry-info">
|
|
<div class="entry-name">${exercise.name || 'Exercise'}</div>
|
|
<div class="entry-details">${details}</div>
|
|
${entry.notes ? `<div class="entry-details">${entry.notes}</div>` : ''}
|
|
</div>
|
|
<span class="badge ${exercise.type === 'cardio' ? 'badge-cardio' : 'badge-strength'}">${exercise.type || 'strength'}</span>
|
|
</div>
|
|
`;
|
|
});
|
|
} else {
|
|
html += '<p class="empty-state">No exercises logged</p>';
|
|
}
|
|
|
|
html += '</div></div>';
|
|
|
|
html += `
|
|
<div class="card-actions mt-4">
|
|
<button class="btn btn-danger" onclick="app.deleteSession(${session.id})">Delete Session</button>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('session-detail-content').innerHTML = html;
|
|
},
|
|
|
|
// Start new session
|
|
startNewSession() {
|
|
this.currentSession = null;
|
|
document.getElementById('session-form').reset();
|
|
document.getElementById('session-date').value = new Date().toISOString().slice(0, 16);
|
|
document.getElementById('session-entries-section').classList.add('hidden');
|
|
document.getElementById('session-entries').innerHTML = '';
|
|
|
|
// Populate plans dropdown
|
|
const planSelect = document.getElementById('session-plan');
|
|
planSelect.innerHTML = '<option value="">No plan</option>' +
|
|
this.plans.map(p => `<option value="${p.id}">${p.name}</option>`).join('');
|
|
|
|
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
document.getElementById('view-log-session').classList.add('active');
|
|
},
|
|
|
|
// Save session
|
|
async saveSession() {
|
|
const date = document.getElementById('session-date').value;
|
|
const planId = document.getElementById('session-plan').value;
|
|
const notes = document.getElementById('session-notes').value;
|
|
|
|
const data = {
|
|
date: new Date(date).toISOString(),
|
|
notes: notes,
|
|
};
|
|
if (planId) {
|
|
data.plan_id = parseInt(planId);
|
|
}
|
|
|
|
try {
|
|
if (this.currentSession) {
|
|
this.currentSession = await api.updateSession(this.currentSession.id, data);
|
|
} else {
|
|
this.currentSession = await api.createSession(data);
|
|
}
|
|
|
|
// Show entries section
|
|
document.getElementById('session-entries-section').classList.remove('hidden');
|
|
this.renderSessionEntries();
|
|
|
|
// Reload sessions
|
|
this.sessions = await api.getSessions();
|
|
} catch (error) {
|
|
alert('Failed to save session');
|
|
}
|
|
},
|
|
|
|
// Render session entries
|
|
renderSessionEntries() {
|
|
const container = document.getElementById('session-entries');
|
|
const entries = this.currentSession?.entries || [];
|
|
|
|
if (entries.length === 0) {
|
|
container.innerHTML = '<p class="empty-state">No exercises yet. Add your first one!</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = entries.map(entry => {
|
|
const exercise = entry.exercise || {};
|
|
let details = '';
|
|
|
|
if (exercise.type === 'cardio') {
|
|
const parts = [];
|
|
if (entry.duration) parts.push(`${Math.floor(entry.duration / 60)} min`);
|
|
if (entry.distance) parts.push(`${entry.distance} km`);
|
|
details = parts.join(' | ');
|
|
} else {
|
|
const parts = [];
|
|
if (entry.weight) parts.push(`${entry.weight} kg`);
|
|
if (entry.sets_completed) parts.push(`${entry.sets_completed} sets`);
|
|
if (entry.reps) parts.push(`${entry.reps} reps`);
|
|
details = parts.join(' x ');
|
|
}
|
|
|
|
return `
|
|
<div class="session-entry">
|
|
<div class="entry-info">
|
|
<div class="entry-name">${exercise.name || 'Exercise'}</div>
|
|
<div class="entry-details">${details}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
},
|
|
|
|
// Show add entry form
|
|
showAddEntryForm() {
|
|
const select = document.getElementById('entry-exercise');
|
|
select.innerHTML = '<option value="">Select exercise</option>' +
|
|
this.exercises.map(e => `<option value="${e.id}" data-type="${e.type}">${e.name}</option>`).join('');
|
|
|
|
document.getElementById('entry-form').reset();
|
|
document.getElementById('strength-fields').classList.remove('hidden');
|
|
document.getElementById('cardio-fields').classList.add('hidden');
|
|
document.getElementById('add-entry-modal').classList.remove('hidden');
|
|
},
|
|
|
|
// Hide add entry form
|
|
hideAddEntryForm() {
|
|
document.getElementById('add-entry-modal').classList.add('hidden');
|
|
},
|
|
|
|
// Update entry fields based on exercise type
|
|
updateEntryFields(exerciseId) {
|
|
const exercise = this.exercises.find(e => e.id === parseInt(exerciseId));
|
|
const isCardio = exercise && exercise.type === 'cardio';
|
|
|
|
document.getElementById('strength-fields').classList.toggle('hidden', isCardio);
|
|
document.getElementById('cardio-fields').classList.toggle('hidden', !isCardio);
|
|
},
|
|
|
|
// Save entry
|
|
async saveEntry() {
|
|
if (!this.currentSession) return;
|
|
|
|
const exerciseId = parseInt(document.getElementById('entry-exercise').value);
|
|
const exercise = this.exercises.find(e => e.id === exerciseId);
|
|
|
|
const data = {
|
|
exercise_id: exerciseId,
|
|
notes: document.getElementById('entry-notes').value,
|
|
};
|
|
|
|
if (exercise && exercise.type === 'cardio') {
|
|
const duration = parseInt(document.getElementById('entry-duration').value);
|
|
if (duration) data.duration = duration * 60; // convert to seconds
|
|
const distance = parseFloat(document.getElementById('entry-distance').value);
|
|
if (distance) data.distance = distance;
|
|
const hr = parseInt(document.getElementById('entry-hr').value);
|
|
if (hr) data.heart_rate = hr;
|
|
} else {
|
|
const weight = parseFloat(document.getElementById('entry-weight').value);
|
|
if (weight) data.weight = weight;
|
|
const sets = parseInt(document.getElementById('entry-sets').value);
|
|
if (sets) data.sets_completed = sets;
|
|
const reps = parseInt(document.getElementById('entry-reps').value);
|
|
if (reps) data.reps = reps;
|
|
}
|
|
|
|
try {
|
|
await api.addSessionEntry(this.currentSession.id, data);
|
|
this.currentSession = await api.getSession(this.currentSession.id);
|
|
this.renderSessionEntries();
|
|
this.hideAddEntryForm();
|
|
} catch (error) {
|
|
alert('Failed to add entry');
|
|
}
|
|
},
|
|
|
|
// Delete session
|
|
async deleteSession(id) {
|
|
if (!confirm('Are you sure you want to delete this session?')) return;
|
|
|
|
try {
|
|
await api.deleteSession(id);
|
|
this.sessions = await api.getSessions();
|
|
this.navigateTo('sessions');
|
|
} catch (error) {
|
|
alert('Failed to delete session');
|
|
}
|
|
},
|
|
|
|
// Render exercises list
|
|
renderExercises() {
|
|
const container = document.getElementById('exercises-list');
|
|
let filtered = this.exercises;
|
|
|
|
if (this.exerciseFilter !== 'all') {
|
|
filtered = this.exercises.filter(e => e.type === this.exerciseFilter);
|
|
}
|
|
|
|
if (filtered.length === 0) {
|
|
container.innerHTML = '<p class="empty-state">No exercises yet</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = filtered.map(exercise => `
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div>
|
|
<div class="card-title">${exercise.name}</div>
|
|
${exercise.muscle_group ? `<div class="card-subtitle">${exercise.muscle_group}</div>` : ''}
|
|
</div>
|
|
<span class="badge ${exercise.type === 'cardio' ? 'badge-cardio' : 'badge-strength'}">${exercise.type}</span>
|
|
</div>
|
|
${exercise.description ? `<div class="card-body">${exercise.description}</div>` : ''}
|
|
<div class="card-actions">
|
|
<button class="btn btn-secondary btn-small" onclick="app.editExercise(${exercise.id})">Edit</button>
|
|
<button class="btn btn-danger btn-small" onclick="app.deleteExercise(${exercise.id})">Delete</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
},
|
|
|
|
// Show exercise form
|
|
showExerciseForm(exercise = null) {
|
|
document.getElementById('exercise-modal-title').textContent = exercise ? 'Edit Exercise' : 'Add Exercise';
|
|
document.getElementById('exercise-id').value = exercise ? exercise.id : '';
|
|
document.getElementById('exercise-name').value = exercise ? exercise.name : '';
|
|
document.getElementById('exercise-type').value = exercise ? exercise.type : 'strength';
|
|
document.getElementById('exercise-muscle').value = exercise ? exercise.muscle_group : '';
|
|
document.getElementById('exercise-description').value = exercise ? exercise.description : '';
|
|
document.getElementById('exercise-modal').classList.remove('hidden');
|
|
},
|
|
|
|
// Hide exercise form
|
|
hideExerciseForm() {
|
|
document.getElementById('exercise-modal').classList.add('hidden');
|
|
},
|
|
|
|
// Edit exercise
|
|
async editExercise(id) {
|
|
const exercise = this.exercises.find(e => e.id === id);
|
|
if (exercise) {
|
|
this.showExerciseForm(exercise);
|
|
}
|
|
},
|
|
|
|
// Save exercise
|
|
async saveExercise() {
|
|
const id = document.getElementById('exercise-id').value;
|
|
const data = {
|
|
name: document.getElementById('exercise-name').value,
|
|
type: document.getElementById('exercise-type').value,
|
|
muscle_group: document.getElementById('exercise-muscle').value,
|
|
description: document.getElementById('exercise-description').value,
|
|
};
|
|
|
|
try {
|
|
if (id) {
|
|
await api.updateExercise(id, data);
|
|
} else {
|
|
await api.createExercise(data);
|
|
}
|
|
this.exercises = await api.getExercises();
|
|
this.hideExerciseForm();
|
|
this.renderExercises();
|
|
} catch (error) {
|
|
alert('Failed to save exercise');
|
|
}
|
|
},
|
|
|
|
// Delete exercise
|
|
async deleteExercise(id) {
|
|
if (!confirm('Are you sure you want to delete this exercise?')) return;
|
|
|
|
try {
|
|
await api.deleteExercise(id);
|
|
this.exercises = await api.getExercises();
|
|
this.renderExercises();
|
|
} catch (error) {
|
|
alert('Failed to delete exercise');
|
|
}
|
|
},
|
|
|
|
// Render plans list
|
|
renderPlans() {
|
|
const container = document.getElementById('plans-list');
|
|
|
|
if (this.plans.length === 0) {
|
|
container.innerHTML = '<p class="empty-state">No training plans yet</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = this.plans.map(plan => `
|
|
<div class="card" onclick="app.viewPlan(${plan.id})">
|
|
<div class="card-header">
|
|
<div>
|
|
<div class="card-title">${plan.name}</div>
|
|
${plan.description ? `<div class="card-subtitle">${plan.description}</div>` : ''}
|
|
</div>
|
|
</div>
|
|
<div class="card-actions">
|
|
<button class="btn btn-secondary btn-small" onclick="event.stopPropagation(); app.editPlan(${plan.id})">Edit</button>
|
|
<button class="btn btn-danger btn-small" onclick="event.stopPropagation(); app.deletePlan(${plan.id})">Delete</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
},
|
|
|
|
// View plan detail
|
|
async viewPlan(id) {
|
|
try {
|
|
this.currentPlan = await api.getPlan(id);
|
|
this.renderPlanDetail();
|
|
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
document.getElementById('view-plan-detail').classList.add('active');
|
|
} catch (error) {
|
|
alert('Failed to load plan');
|
|
}
|
|
},
|
|
|
|
// Render plan detail
|
|
renderPlanDetail() {
|
|
const plan = this.currentPlan;
|
|
if (!plan) return;
|
|
|
|
document.getElementById('plan-detail-title').textContent = plan.name;
|
|
|
|
let html = '';
|
|
if (plan.description) {
|
|
html += `<p class="card-subtitle">${plan.description}</p>`;
|
|
}
|
|
|
|
html += '<div class="section"><h3>Exercises</h3><div class="card-list">';
|
|
|
|
if (plan.exercises && plan.exercises.length > 0) {
|
|
plan.exercises.forEach((pe, index) => {
|
|
const exercise = pe.exercise || {};
|
|
let details = [];
|
|
if (pe.sets) details.push(`${pe.sets} sets`);
|
|
if (pe.reps) details.push(`${pe.reps} reps`);
|
|
if (pe.duration) details.push(`${Math.floor(pe.duration / 60)} min`);
|
|
|
|
html += `
|
|
<div class="session-entry">
|
|
<div class="entry-info">
|
|
<div class="entry-name">${index + 1}. ${exercise.name || 'Exercise'}</div>
|
|
<div class="entry-details">${details.join(' x ') || 'No targets set'}</div>
|
|
</div>
|
|
<span class="badge ${exercise.type === 'cardio' ? 'badge-cardio' : 'badge-strength'}">${exercise.type || 'strength'}</span>
|
|
</div>
|
|
`;
|
|
});
|
|
} else {
|
|
html += '<p class="empty-state">No exercises in this plan</p>';
|
|
}
|
|
|
|
html += '</div></div>';
|
|
|
|
html += `
|
|
<div class="card-actions mt-4">
|
|
<button class="btn btn-primary" onclick="app.startSessionFromPlan(${plan.id})">Start Session</button>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('plan-detail-content').innerHTML = html;
|
|
},
|
|
|
|
// Start session from plan
|
|
startSessionFromPlan(planId) {
|
|
this.startNewSession();
|
|
document.getElementById('session-plan').value = planId;
|
|
},
|
|
|
|
// Show plan form
|
|
showPlanForm(plan = null) {
|
|
document.getElementById('plan-modal-title').textContent = plan ? 'Edit Plan' : 'Add Training Plan';
|
|
document.getElementById('plan-id').value = plan ? plan.id : '';
|
|
document.getElementById('plan-name').value = plan ? plan.name : '';
|
|
document.getElementById('plan-description').value = plan ? plan.description : '';
|
|
|
|
const exercisesList = document.getElementById('plan-exercises-list');
|
|
exercisesList.innerHTML = '';
|
|
|
|
if (plan && plan.exercises) {
|
|
plan.exercises.forEach(pe => this.addPlanExerciseRow(pe));
|
|
}
|
|
|
|
document.getElementById('plan-modal').classList.remove('hidden');
|
|
},
|
|
|
|
// Hide plan form
|
|
hidePlanForm() {
|
|
document.getElementById('plan-modal').classList.add('hidden');
|
|
},
|
|
|
|
// Add plan exercise row
|
|
addPlanExercise() {
|
|
this.addPlanExerciseRow();
|
|
},
|
|
|
|
addPlanExerciseRow(data = null) {
|
|
const container = document.getElementById('plan-exercises-list');
|
|
const index = container.children.length;
|
|
|
|
const div = document.createElement('div');
|
|
div.className = 'plan-exercise-item';
|
|
div.innerHTML = `
|
|
<select name="exercise_id" required>
|
|
<option value="">Select</option>
|
|
${this.exercises.map(e => `<option value="${e.id}" ${data && data.exercise_id === e.id ? 'selected' : ''}>${e.name}</option>`).join('')}
|
|
</select>
|
|
<input type="number" name="sets" placeholder="Sets" min="0" value="${data?.sets || ''}">
|
|
<input type="number" name="reps" placeholder="Reps" min="0" value="${data?.reps || ''}">
|
|
<button type="button" class="btn btn-danger btn-small" onclick="this.parentElement.remove()">X</button>
|
|
`;
|
|
container.appendChild(div);
|
|
},
|
|
|
|
// Edit plan
|
|
async editPlan(id) {
|
|
try {
|
|
const plan = await api.getPlan(id);
|
|
this.showPlanForm(plan);
|
|
} catch (error) {
|
|
alert('Failed to load plan');
|
|
}
|
|
},
|
|
|
|
// Save plan
|
|
async savePlan() {
|
|
const id = document.getElementById('plan-id').value;
|
|
const exercises = [];
|
|
|
|
document.querySelectorAll('#plan-exercises-list .plan-exercise-item').forEach((item, index) => {
|
|
const exerciseId = item.querySelector('[name="exercise_id"]').value;
|
|
const sets = item.querySelector('[name="sets"]').value;
|
|
const reps = item.querySelector('[name="reps"]').value;
|
|
|
|
if (exerciseId) {
|
|
exercises.push({
|
|
exercise_id: parseInt(exerciseId),
|
|
sets: sets ? parseInt(sets) : 0,
|
|
reps: reps ? parseInt(reps) : 0,
|
|
order: index,
|
|
});
|
|
}
|
|
});
|
|
|
|
const data = {
|
|
name: document.getElementById('plan-name').value,
|
|
description: document.getElementById('plan-description').value,
|
|
exercises: exercises,
|
|
};
|
|
|
|
try {
|
|
if (id) {
|
|
await api.updatePlan(id, data);
|
|
} else {
|
|
await api.createPlan(data);
|
|
}
|
|
this.plans = await api.getPlans();
|
|
this.hidePlanForm();
|
|
this.renderPlans();
|
|
} catch (error) {
|
|
alert('Failed to save plan');
|
|
}
|
|
},
|
|
|
|
// Delete plan
|
|
async deletePlan(id) {
|
|
if (!confirm('Are you sure you want to delete this plan?')) return;
|
|
|
|
try {
|
|
await api.deletePlan(id);
|
|
this.plans = await api.getPlans();
|
|
this.renderPlans();
|
|
} catch (error) {
|
|
alert('Failed to delete plan');
|
|
}
|
|
},
|
|
};
|
|
|
|
// Initialize app when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
app.init();
|
|
});
|