// 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 = '

No sessions yet. Start your first workout!

'; 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 = '

No sessions yet

'; 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 `
${date}
${planName}
${entryCount} exercises
${session.notes ? `
${session.notes}
` : ''}
`; }, // 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 += `

Plan: ${session.plan.name}

`; } if (session.notes) { html += `

${session.notes}

`; } html += '

Exercises

'; 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 += `
${exercise.type || 'strength'}
`; }); } else { html += '

No exercises logged

'; } html += '
'; html += `
`; 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 = '' + this.plans.map(p => ``).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 = '

No exercises yet. Add your first one!

'; 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 `
${exercise.name || 'Exercise'}
${details}
`; }).join(''); }, // Show add entry form showAddEntryForm() { const select = document.getElementById('entry-exercise'); select.innerHTML = '' + this.exercises.map(e => ``).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 = '

No exercises yet

'; return; } container.innerHTML = filtered.map(exercise => `
${exercise.name}
${exercise.muscle_group ? `
${exercise.muscle_group}
` : ''}
${exercise.type}
${exercise.description ? `
${exercise.description}
` : ''}
`).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 = '

No training plans yet

'; return; } container.innerHTML = this.plans.map(plan => `
${plan.name}
${plan.description ? `
${plan.description}
` : ''}
`).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 += `

${plan.description}

`; } html += '

Exercises

'; 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 += `
${exercise.type || 'strength'}
`; }); } else { html += '

No exercises in this plan

'; } html += '
'; html += `
`; 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 = ` `; 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(); });