Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Workout Logger - Push Legs Pull Split</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.fade-in { | |
animation: fadeIn 0.3s ease-in-out; | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; transform: translateY(10px); } | |
to { opacity: 1; transform: translateY(0); } | |
} | |
.workout-day.active { | |
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5); | |
} | |
.progress-bar { | |
transition: width 0.5s ease-in-out; | |
} | |
.history-item { | |
transition: all 0.2s ease; | |
cursor: pointer; | |
} | |
.history-item:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
} | |
.history-item.active { | |
border-left: 4px solid #3b82f6; | |
} | |
.history-exercises { | |
max-height: 0; | |
overflow: hidden; | |
transition: max-height 0.3s ease; | |
} | |
.history-exercises.show { | |
max-height: 1000px; | |
} | |
.edit-history-form { | |
background-color: rgba(243, 244, 246, 0.8); | |
backdrop-filter: blur(5px); | |
} | |
</style> | |
</head> | |
<body class="bg-gray-100 min-h-screen"> | |
<div class="container mx-auto px-4 py-8 max-w-4xl"> | |
<!-- Header --> | |
<header class="mb-8 text-center"> | |
<h1 class="text-4xl font-bold text-blue-600 mb-2">Workout Logger</h1> | |
<p class="text-gray-600">Track your Push, Legs, Pull split progress</p> | |
</header> | |
<!-- Week Navigation --> | |
<div class="bg-white rounded-xl shadow-md p-4 mb-6"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold text-gray-800">This Week</h2> | |
<div class="flex space-x-2"> | |
<button id="prev-week" class="p-2 rounded-full bg-gray-200 hover:bg-gray-300"> | |
<i class="fas fa-chevron-left"></i> | |
</button> | |
<button id="next-week" class="p-2 rounded-full bg-gray-200 hover:bg-gray-300"> | |
<i class="fas fa-chevron-right"></i> | |
</button> | |
</div> | |
</div> | |
<div class="grid grid-cols-6 gap-2"> | |
<div class="workout-day rounded-lg p-3 text-center cursor-pointer transition-all" data-day="monday" data-type="push"> | |
<div class="font-medium">Monday</div> | |
<div class="text-xs text-blue-500">Push</div> | |
</div> | |
<div class="workout-day rounded-lg p-3 text-center cursor-pointer transition-all" data-day="tuesday" data-type="pull"> | |
<div class="font-medium">Tuesday</div> | |
<div class="text-xs text-green-500">Pull</div> | |
</div> | |
<div class="workout-day rounded-lg p-3 text-center cursor-pointer transition-all" data-day="wednesday" data-type="legs"> | |
<div class="font-medium">Wednesday</div> | |
<div class="text-xs text-purple-500">Legs</div> | |
</div> | |
<div class="workout-day rounded-lg p-3 text-center cursor-pointer transition-all" data-day="thursday" data-type="push"> | |
<div class="font-medium">Thursday</div> | |
<div class="text-xs text-blue-500">Push</div> | |
</div> | |
<div class="workout-day rounded-lg p-3 text-center cursor-pointer transition-all" data-day="friday" data-type="pull"> | |
<div class="font-medium">Friday</div> | |
<div class="text-xs text-green-500">Pull</div> | |
</div> | |
<div class="workout-day rounded-lg p-3 text-center cursor-pointer transition-all" data-day="saturday" data-type="legs"> | |
<div class="font-medium">Saturday</div> | |
<div class="text-xs text-purple-500">Legs</div> | |
</div> | |
</div> | |
</div> | |
<!-- Current Workout --> | |
<div id="current-workout" class="bg-white rounded-xl shadow-md p-6 mb-6 fade-in"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold text-gray-800"> | |
<span id="current-day">Monday</span> - | |
<span id="workout-type" class="text-blue-500">Push</span> | |
</h2> | |
<div class="text-sm text-gray-500" id="workout-date">June 12, 2023</div> | |
</div> | |
<!-- Progress --> | |
<div class="mb-6"> | |
<div class="flex justify-between text-sm mb-1"> | |
<span>Workout Progress</span> | |
<span id="progress-percent">0%</span> | |
</div> | |
<div class="w-full bg-gray-200 rounded-full h-2.5"> | |
<div id="progress-bar" class="progress-bar h-2.5 rounded-full bg-blue-500" style="width: 0%"></div> | |
</div> | |
</div> | |
<!-- Exercises List --> | |
<div id="exercises-container" class="mb-6"> | |
<!-- Exercises will be added here --> | |
</div> | |
<!-- Add Exercise Form --> | |
<div class="bg-gray-50 p-4 rounded-lg"> | |
<h3 class="font-medium mb-3">Add New Exercise</h3> | |
<div class="grid grid-cols-1 md:grid-cols-4 gap-3"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Exercise</label> | |
<input type="text" id="exercise-name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Bench Press"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Weight (lbs)</label> | |
<input type="number" id="exercise-weight" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="45"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Sets</label> | |
<input type="number" id="exercise-sets" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="3" value="3"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Reps</label> | |
<input type="number" id="exercise-reps" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="10" value="10"> | |
</div> | |
</div> | |
<button id="add-exercise" class="mt-4 w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md transition-colors"> | |
<i class="fas fa-plus mr-2"></i> Add Exercise | |
</button> | |
</div> | |
</div> | |
<!-- Workout History --> | |
<div class="bg-white rounded-xl shadow-md p-6"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold text-gray-800">Workout History</h2> | |
<button id="toggle-history-view" class="text-sm text-blue-600 hover:text-blue-800"> | |
<i class="fas fa-list mr-1"></i> Toggle View | |
</button> | |
</div> | |
<div id="history-container"> | |
<!-- History items will be added here --> | |
<div class="text-center py-8 text-gray-400"> | |
<i class="fas fa-dumbbell text-4xl mb-2"></i> | |
<p>Your workout history will appear here</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Edit History Modal --> | |
<div id="edit-history-modal" class="fixed inset-0 z-50 flex items-center justify-center hidden"> | |
<div class="absolute inset-0 bg-black bg-opacity-50"></div> | |
<div class="edit-history-form relative bg-white rounded-xl shadow-xl p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="text-xl font-semibold">Edit Workout</h3> | |
<button id="close-edit-modal" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div id="edit-history-content"> | |
<!-- Content will be added here --> | |
</div> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// DOM Elements | |
const workoutDays = document.querySelectorAll('.workout-day'); | |
const currentDayElement = document.getElementById('current-day'); | |
const workoutTypeElement = document.getElementById('workout-type'); | |
const workoutDateElement = document.getElementById('workout-date'); | |
const exercisesContainer = document.getElementById('exercises-container'); | |
const historyContainer = document.getElementById('history-container'); | |
const progressBar = document.getElementById('progress-bar'); | |
const progressPercent = document.getElementById('progress-percent'); | |
const toggleHistoryViewBtn = document.getElementById('toggle-history-view'); | |
const editHistoryModal = document.getElementById('edit-history-modal'); | |
const editHistoryContent = document.getElementById('edit-history-content'); | |
const closeEditModalBtn = document.getElementById('close-edit-modal'); | |
// Form elements | |
const exerciseNameInput = document.getElementById('exercise-name'); | |
const exerciseWeightInput = document.getElementById('exercise-weight'); | |
const exerciseSetsInput = document.getElementById('exercise-sets'); | |
const exerciseRepsInput = document.getElementById('exercise-reps'); | |
const addExerciseBtn = document.getElementById('add-exercise'); | |
// Data | |
let currentDay = 'monday'; | |
let currentWorkoutType = 'push'; | |
let workouts = { | |
monday: { exercises: [], completed: false, date: new Date().toISOString() }, | |
tuesday: { exercises: [], completed: false, date: new Date().toISOString() }, | |
wednesday: { exercises: [], completed: false, date: new Date().toISOString() }, | |
thursday: { exercises: [], completed: false, date: new Date().toISOString() }, | |
friday: { exercises: [], completed: false, date: new Date().toISOString() }, | |
saturday: { exercises: [], completed: false, date: new Date().toISOString() } | |
}; | |
// View state | |
let historyViewMode = 'detailed'; // 'detailed' or 'compact' | |
// Initialize the app | |
initApp(); | |
// Event Listeners | |
workoutDays.forEach(day => { | |
day.addEventListener('click', function() { | |
currentDay = this.dataset.day; | |
currentWorkoutType = this.dataset.type; | |
updateUI(); | |
}); | |
}); | |
addExerciseBtn.addEventListener('click', addExercise); | |
toggleHistoryViewBtn.addEventListener('click', toggleHistoryView); | |
closeEditModalBtn.addEventListener('click', () => editHistoryModal.classList.add('hidden')); | |
// Functions | |
function initApp() { | |
// Load data from localStorage if available | |
const savedWorkouts = localStorage.getItem('workoutLoggerData'); | |
if (savedWorkouts) { | |
workouts = JSON.parse(savedWorkouts); | |
// Ensure all dates are properly formatted | |
Object.keys(workouts).forEach(day => { | |
if (workouts[day].date) { | |
// If date is already in ISO format, keep it | |
if (typeof workouts[day].date === 'string' && workouts[day].date.includes('T')) { | |
return; | |
} | |
// Otherwise, convert to ISO format | |
workouts[day].date = new Date(workouts[day].date).toISOString(); | |
} else { | |
workouts[day].date = new Date().toISOString(); | |
} | |
// Ensure each exercise has a date | |
workouts[day].exercises.forEach(exercise => { | |
if (!exercise.date) { | |
exercise.date = workouts[day].date; | |
} else if (!exercise.date.includes('T')) { | |
exercise.date = new Date(exercise.date).toISOString(); | |
} | |
}); | |
}); | |
} | |
// Set current date | |
updateDate(); | |
// Initialize UI | |
updateUI(); | |
loadHistory(); | |
} | |
function updateDate() { | |
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; | |
const today = new Date(); | |
workoutDateElement.textContent = today.toLocaleDateString('en-US', options); | |
} | |
function updateUI() { | |
// Update active day | |
workoutDays.forEach(day => { | |
day.classList.remove('active', 'bg-blue-50', 'bg-green-50', 'bg-purple-50'); | |
if (day.dataset.day === currentDay) { | |
day.classList.add('active'); | |
if (day.dataset.type === 'push') day.classList.add('bg-blue-50'); | |
if (day.dataset.type === 'pull') day.classList.add('bg-green-50'); | |
if (day.dataset.type === 'legs') day.classList.add('bg-purple-50'); | |
} | |
}); | |
// Update header | |
currentDayElement.textContent = currentDay.charAt(0).toUpperCase() + currentDay.slice(1); | |
workoutTypeElement.textContent = currentWorkoutType.charAt(0).toUpperCase() + currentWorkoutType.slice(1); | |
// Update color based on workout type | |
workoutTypeElement.className = ''; | |
if (currentWorkoutType === 'push') workoutTypeElement.classList.add('text-blue-500'); | |
if (currentWorkoutType === 'pull') workoutTypeElement.classList.add('text-green-500'); | |
if (currentWorkoutType === 'legs') workoutTypeElement.classList.add('text-purple-500'); | |
// Update exercises list | |
renderExercises(); | |
// Update progress | |
updateProgress(); | |
} | |
function renderExercises() { | |
exercisesContainer.innerHTML = ''; | |
const dayExercises = workouts[currentDay].exercises; | |
if (dayExercises.length === 0) { | |
exercisesContainer.innerHTML = ` | |
<div class="text-center py-8 text-gray-400"> | |
<i class="fas fa-plus-circle text-4xl mb-2"></i> | |
<p>Add your first exercise to get started</p> | |
</div> | |
`; | |
return; | |
} | |
dayExercises.forEach((exercise, index) => { | |
const exerciseElement = document.createElement('div'); | |
exerciseElement.className = 'exercise-item bg-gray-50 rounded-lg p-4 mb-3 fade-in'; | |
exerciseElement.innerHTML = ` | |
<div class="flex justify-between items-start mb-2"> | |
<h3 class="font-medium">${exercise.name}</h3> | |
<div class="flex space-x-2"> | |
<button class="edit-exercise p-1 text-blue-500 hover:text-blue-700" data-index="${index}"> | |
<i class="fas fa-edit"></i> | |
</button> | |
<button class="delete-exercise p-1 text-red-500 hover:text-red-700" data-index="${index}"> | |
<i class="fas fa-trash"></i> | |
</button> | |
</div> | |
</div> | |
<div class="grid grid-cols-3 gap-2 text-sm"> | |
<div class="bg-white p-2 rounded"> | |
<div class="text-gray-500">Weight</div> | |
<div>${exercise.weight} lbs</div> | |
</div> | |
<div class="bg-white p-2 rounded"> | |
<div class="text-gray-500">Sets</div> | |
<div>${exercise.sets}</div> | |
</div> | |
<div class="bg-white p-2 rounded"> | |
<div class="text-gray-500">Reps</div> | |
<div>${exercise.reps}</div> | |
</div> | |
</div> | |
<div class="text-xs text-gray-400 mt-1">${formatShortDate(exercise.date)}</div> | |
`; | |
exercisesContainer.appendChild(exerciseElement); | |
}); | |
// Add event listeners to delete buttons | |
document.querySelectorAll('.delete-exercise').forEach(btn => { | |
btn.addEventListener('click', function() { | |
const index = parseInt(this.dataset.index); | |
deleteExercise(index); | |
}); | |
}); | |
// Add event listeners to edit buttons | |
document.querySelectorAll('.edit-exercise').forEach(btn => { | |
btn.addEventListener('click', function() { | |
const index = parseInt(this.dataset.index); | |
editExercise(index); | |
}); | |
}); | |
} | |
function addExercise() { | |
const name = exerciseNameInput.value.trim(); | |
const weight = parseFloat(exerciseWeightInput.value); | |
const sets = parseInt(exerciseSetsInput.value); | |
const reps = parseInt(exerciseRepsInput.value); | |
if (!name || isNaN(weight) || isNaN(sets) || isNaN(reps)) { | |
alert('Please fill in all fields with valid values'); | |
return; | |
} | |
const newExercise = { | |
name, | |
weight, | |
sets, | |
reps, | |
date: new Date().toISOString() | |
}; | |
workouts[currentDay].exercises.push(newExercise); | |
workouts[currentDay].completed = workouts[currentDay].exercises.length > 0; | |
workouts[currentDay].date = new Date().toISOString(); | |
saveData(); | |
updateUI(); | |
loadHistory(); | |
// Reset form | |
exerciseNameInput.value = ''; | |
exerciseWeightInput.value = ''; | |
exerciseSetsInput.value = '3'; | |
exerciseRepsInput.value = '10'; | |
exerciseNameInput.focus(); | |
} | |
function deleteExercise(index) { | |
if (confirm('Are you sure you want to delete this exercise?')) { | |
workouts[currentDay].exercises.splice(index, 1); | |
workouts[currentDay].completed = workouts[currentDay].exercises.length > 0; | |
saveData(); | |
updateUI(); | |
loadHistory(); | |
} | |
} | |
function editExercise(index) { | |
const exercise = workouts[currentDay].exercises[index]; | |
// Fill the form with exercise data | |
exerciseNameInput.value = exercise.name; | |
exerciseWeightInput.value = exercise.weight; | |
exerciseSetsInput.value = exercise.sets; | |
exerciseRepsInput.value = exercise.reps; | |
// Delete the exercise (we'll add it again when saved) | |
workouts[currentDay].exercises.splice(index, 1); | |
updateUI(); | |
loadHistory(); | |
// Focus on the name field | |
exerciseNameInput.focus(); | |
} | |
function updateProgress() { | |
const dayExercises = workouts[currentDay].exercises; | |
const totalExercises = dayExercises.length; | |
// Simple progress calculation (could be enhanced) | |
const progress = totalExercises > 0 ? Math.min(100, totalExercises * 20) : 0; | |
progressBar.style.width = `${progress}%`; | |
progressPercent.textContent = `${progress}%`; | |
// Change color based on progress | |
progressBar.className = 'progress-bar h-2.5 rounded-full'; | |
if (progress < 30) progressBar.classList.add('bg-red-500'); | |
else if (progress < 70) progressBar.classList.add('bg-yellow-500'); | |
else progressBar.classList.add('bg-green-500'); | |
} | |
function saveData() { | |
localStorage.setItem('workoutLoggerData', JSON.stringify(workouts)); | |
} | |
function loadHistory() { | |
historyContainer.innerHTML = ''; | |
// Get all workout days that have exercises | |
const workoutDaysWithData = Object.entries(workouts) | |
.filter(([day, data]) => data.exercises.length > 0) | |
.sort((a, b) => new Date(b[1].date) - new Date(a[1].date)); | |
if (workoutDaysWithData.length === 0) { | |
historyContainer.innerHTML = ` | |
<div class="text-center py-8 text-gray-400"> | |
<i class="fas fa-dumbbell text-4xl mb-2"></i> | |
<p>Your workout history will appear here</p> | |
</div> | |
`; | |
return; | |
} | |
workoutDaysWithData.forEach(([day, workoutData]) => { | |
const historyItem = document.createElement('div'); | |
historyItem.className = 'history-item bg-gray-50 rounded-lg p-4 mb-3'; | |
if (historyViewMode === 'detailed') { | |
historyItem.innerHTML = ` | |
<div class="flex justify-between items-center mb-2"> | |
<div> | |
<h3 class="font-medium">${day.charAt(0).toUpperCase() + day.slice(1)} - | |
<span class="${getWorkoutTypeColorClass(day)}"> | |
${getWorkoutType(day)} | |
</span></h3> | |
<div class="text-xs text-gray-500">${formatDate(workoutData.date)}</div> | |
</div> | |
<div class="flex space-x-2"> | |
<button class="edit-history p-1 text-blue-500 hover:text-blue-700" data-day="${day}"> | |
<i class="fas fa-edit"></i> | |
</button> | |
<span class="text-sm text-gray-500">${workoutData.exercises.length} ex.</span> | |
</div> | |
</div> | |
<div class="flex space-x-2 text-sm mb-2"> | |
<span class="bg-blue-100 text-blue-800 px-2 py-1 rounded">${workoutData.completed ? 'Completed' : 'In Progress'}</span> | |
<span class="bg-green-100 text-green-800 px-2 py-1 rounded">${formatShortDate(workoutData.date)}</span> | |
</div> | |
<div class="history-exercises"> | |
${renderHistoryExercises(workoutData.exercises)} | |
</div> | |
`; | |
} else { | |
historyItem.innerHTML = ` | |
<div class="flex justify-between items-center"> | |
<div> | |
<h3 class="font-medium">${day.charAt(0).toUpperCase() + day.slice(1)} - | |
<span class="${getWorkoutTypeColorClass(day)}"> | |
${getWorkoutType(day)} | |
</span></h3> | |
<div class="text-xs text-gray-500">${formatDate(workoutData.date)}</div> | |
</div> | |
<div class="flex space-x-2 items-center"> | |
<button class="edit-history p-1 text-blue-500 hover:text-blue-700" data-day="${day}"> | |
<i class="fas fa-edit"></i> | |
</button> | |
<span class="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded">${workoutData.exercises.length} ex.</span> | |
</div> | |
</div> | |
`; | |
} | |
historyContainer.appendChild(historyItem); | |
// Add click event to toggle exercises visibility (detailed view only) | |
if (historyViewMode === 'detailed') { | |
const header = historyItem.querySelector('.flex.justify-between.items-center'); | |
const exercisesSection = historyItem.querySelector('.history-exercises'); | |
header.addEventListener('click', function(e) { | |
// Don't toggle if clicking the edit button | |
if (!e.target.closest('.edit-history')) { | |
historyItem.classList.toggle('active'); | |
exercisesSection.classList.toggle('show'); | |
} | |
}); | |
} | |
// Add event listener to edit button | |
historyItem.querySelector('.edit-history').addEventListener('click', function() { | |
const day = this.dataset.day; | |
openEditHistoryModal(day); | |
}); | |
}); | |
} | |
function renderHistoryExercises(exercises) { | |
if (exercises.length === 0) return ''; | |
let html = '<div class="space-y-2">'; | |
exercises.forEach((exercise, index) => { | |
html += ` | |
<div class="bg-white p-3 rounded-lg border border-gray-200"> | |
<div class="flex justify-between items-start"> | |
<div class="font-medium">${exercise.name}</div> | |
<div class="flex space-x-2"> | |
<button class="edit-history-exercise p-1 text-blue-500 hover:text-blue-700" data-index="${index}"> | |
<i class="fas fa-edit"></i> | |
</button> | |
<button class="delete-history-exercise p-1 text-red-500 hover:text-red-700" data-index="${index}"> | |
<i class="fas fa-trash"></i> | |
</button> | |
</div> | |
</div> | |
<div class="grid grid-cols-3 gap-2 text-sm mt-2"> | |
<div> | |
<div class="text-gray-500">Weight</div> | |
<div>${exercise.weight} lbs</div> | |
</div> | |
<div> | |
<div class="text-gray-500">Sets</div> | |
<div>${exercise.sets}</div> | |
</div> | |
<div> | |
<div class="text-gray-500">Reps</div> | |
<div>${exercise.reps}</div> | |
</div> | |
</div> | |
<div class="text-xs text-gray-400 mt-1">${formatShortDate(exercise.date)}</div> | |
</div> | |
`; | |
}); | |
html += '</div>'; | |
return html; | |
} | |
function openEditHistoryModal(day) { | |
const workoutData = workouts[day]; | |
editHistoryContent.innerHTML = ` | |
<div class="mb-4"> | |
<h4 class="font-medium text-lg mb-2">${day.charAt(0).toUpperCase() + day.slice(1)} Workout</h4> | |
<div class="text-sm text-gray-600 mb-4">${formatDate(workoutData.date)}</div> | |
<div class="flex items-center mb-4"> | |
<label class="block text-sm font-medium text-gray-700 mr-2">Completed:</label> | |
<input type="checkbox" id="edit-completed" ${workoutData.completed ? 'checked' : ''} class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"> | |
</div> | |
<div class="mb-2"> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Workout Date</label> | |
<input type="datetime-local" id="edit-workout-date" value="${formatDateTimeLocal(workoutData.date)}" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
</div> | |
</div> | |
<div class="border-t border-gray-200 pt-4"> | |
<h5 class="font-medium mb-3">Exercises</h5> | |
<div id="edit-exercises-list" class="space-y-3 mb-4"> | |
${renderEditExercisesList(workoutData.exercises)} | |
</div> | |
<div class="bg-gray-50 p-3 rounded-lg"> | |
<h6 class="font-medium mb-2">Add New Exercise</h6> | |
<div class="grid grid-cols-1 md:grid-cols-4 gap-2"> | |
<div> | |
<input type="text" id="new-exercise-name" placeholder="Exercise" class="w-full px-2 py-1 border border-gray-300 rounded-md text-sm"> | |
</div> | |
<div> | |
<input type="number" id="new-exercise-weight" placeholder="Weight" class="w-full px-2 py-1 border border-gray-300 rounded-md text-sm"> | |
</div> | |
<div> | |
<input type="number" id="new-exercise-sets" placeholder="Sets" class="w-full px-2 py-1 border border-gray-300 rounded-md text-sm"> | |
</div> | |
<div> | |
<input type="number" id="new-exercise-reps" placeholder="Reps" class="w-full px-2 py-1 border border-gray-300 rounded-md text-sm"> | |
</div> | |
</div> | |
<button id="add-new-exercise" class="mt-2 w-full bg-blue-600 hover:bg-blue-700 text-white py-1 px-3 rounded-md text-sm transition-colors"> | |
<i class="fas fa-plus mr-1"></i> Add Exercise | |
</button> | |
</div> | |
</div> | |
<div class="flex justify-end space-x-3 mt-6"> | |
<button id="cancel-edit" class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50"> | |
Cancel | |
</button> | |
<button id="save-edit" data-day="${day}" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md text-sm font-medium"> | |
Save Changes | |
</button> | |
</div> | |
`; | |
// Add event listeners to delete buttons | |
editHistoryContent.querySelectorAll('.delete-history-exercise').forEach(btn => { | |
btn.addEventListener('click', function() { | |
const index = parseInt(this.dataset.index); | |
if (confirm('Are you sure you want to delete this exercise?')) { | |
workoutData.exercises.splice(index, 1); | |
openEditHistoryModal(day); // Refresh the modal | |
} | |
}); | |
}); | |
// Add event listener to add new exercise button | |
editHistoryContent.querySelector('#add-new-exercise').addEventListener('click', function() { | |
const name = editHistoryContent.querySelector('#new-exercise-name').value.trim(); | |
const weight = parseFloat(editHistoryContent.querySelector('#new-exercise-weight').value); | |
const sets = parseInt(editHistoryContent.querySelector('#new-exercise-sets').value); | |
const reps = parseInt(editHistoryContent.querySelector('#new-exercise-reps').value); | |
if (!name || isNaN(weight) || isNaN(sets) || isNaN(reps)) { | |
alert('Please fill in all fields with valid values'); | |
return; | |
} | |
workoutData.exercises.push({ | |
name, | |
weight, | |
sets, | |
reps, | |
date: new Date().toISOString() | |
}); | |
openEditHistoryModal(day); // Refresh the modal | |
}); | |
// Add event listener to save button | |
editHistoryContent.querySelector('#save-edit').addEventListener('click', function() { | |
const day = this.dataset.day; | |
const completed = editHistoryContent.querySelector('#edit-completed').checked; | |
const date = new Date(editHistoryContent.querySelector('#edit-workout-date').value).toISOString(); | |
workouts[day].completed = completed; | |
workouts[day].date = date; | |
// Update all exercises dates if workout date changed | |
if (workouts[day].date !== workoutData.date) { | |
workouts[day].exercises.forEach(exercise => { | |
exercise.date = workouts[day].date; | |
}); | |
} | |
saveData(); | |
updateUI(); | |
loadHistory(); | |
editHistoryModal.classList.add('hidden'); | |
}); | |
// Add event listener to cancel button | |
editHistoryContent.querySelector('#cancel-edit').addEventListener('click', function() { | |
editHistoryModal.classList.add('hidden'); | |
}); | |
editHistoryModal.classList.remove('hidden'); | |
} | |
function renderEditExercisesList(exercises) { | |
if (exercises.length === 0) return '<div class="text-center py-4 text-gray-400">No exercises</div>'; | |
let html = ''; | |
exercises.forEach((exercise, index) => { | |
html += ` | |
<div class="bg-white p-3 rounded-lg border border-gray-200"> | |
<div class="grid grid-cols-1 md:grid-cols-5 gap-2"> | |
<div> | |
<label class="text-xs text-gray-500">Exercise</label> | |
<input type="text" value="${exercise.name}" data-index="${index}" data-field="name" class="w-full px-2 py-1 border border-gray-300 rounded-md text-sm"> | |
</div> | |
<div> | |
<label class="text-xs text-gray-500">Weight</label> | |
<input type="number" value="${exercise.weight}" data-index="${index}" data-field="weight" class="w-full px-2 py-1 border border-gray-300 rounded-md text-sm"> | |
</div> | |
<div> | |
<label class="text-xs text-gray-500">Sets</label> | |
<input type="number" value="${exercise.sets}" data-index="${index}" data-field="sets" class="w-full px-2 py-1 border border-gray-300 rounded-md text-sm"> | |
</div> | |
<div> | |
<label class="text-xs text-gray-500">Reps</label> | |
<input type="number" value="${exercise.reps}" data-index="${index}" data-field="reps" class="w-full px-2 py-1 border border-gray-300 rounded-md text-sm"> | |
</div> | |
<div> | |
<label class="text-xs text-gray-500">Actions</label> | |
<button class="delete-history-exercise w-full px-2 py-1 bg-red-500 hover:bg-red-600 text-white rounded-md text-sm" data-index="${index}"> | |
<i class="fas fa-trash mr-1"></i> Delete | |
</button> | |
</div> | |
</div> | |
<div class="mt-2"> | |
<label class="text-xs text-gray-500">Date</label> | |
<input type="datetime-local" value="${formatDateTimeLocal(exercise.date)}" data-index="${index}" data-field="date" class="w-full px-2 py-1 border border-gray-300 rounded-md text-sm"> | |
</div> | |
</div> | |
`; | |
}); | |
// Add input change listeners | |
setTimeout(() => { | |
editHistoryContent.querySelectorAll('input[data-index]').forEach(input => { | |
input.addEventListener('change', function() { | |
const index = parseInt(this.dataset.index); | |
const field = this.dataset.field; | |
const value = field === 'name' ? this.value : | |
field === 'date' ? new Date(this.value).toISOString() : | |
parseFloat(this.value); | |
workouts[currentDay].exercises[index][field] = value; | |
}); | |
}); | |
}, 0); | |
return html; | |
} | |
function toggleHistoryView() { | |
historyViewMode = historyViewMode === 'detailed' ? 'compact' : 'detailed'; | |
toggleHistoryViewBtn.innerHTML = historyViewMode === 'detailed' ? | |
'<i class="fas fa-list mr-1"></i> Compact View' : | |
'<i class="fas fa-list mr-1"></i> Detailed View'; | |
loadHistory(); | |
} | |
function getWorkoutType(day) { | |
if (day === 'monday' || day === 'thursday') return 'Push'; | |
if (day === 'tuesday' || day === 'friday') return 'Pull'; | |
if (day === 'wednesday' || day === 'saturday') return 'Legs'; | |
return ''; | |
} | |
function getWorkoutTypeColorClass(day) { | |
if (day === 'monday' || day === 'thursday') return 'text-blue-500'; | |
if (day === 'tuesday' || day === 'friday') return 'text-green-500'; | |
if (day === 'wednesday' || day === 'saturday') return 'text-purple-500'; | |
return ''; | |
} | |
function formatDate(dateString) { | |
try { | |
const date = new Date(dateString); | |
if (isNaN(date.getTime())) { | |
return 'Date not available'; | |
} | |
return date.toLocaleDateString('en-US', { | |
weekday: 'long', | |
month: 'short', | |
day: 'numeric', | |
year: 'numeric', | |
hour: '2-digit', | |
minute: '2-digit' | |
}); | |
} catch (e) { | |
console.error('Error formatting date:', e); | |
return 'Date not available'; | |
} | |
} | |
function formatShortDate(dateString) { | |
try { | |
const date = new Date(dateString); | |
if (isNaN(date.getTime())) { | |
return ''; | |
} | |
return date.toLocaleDateString('en-US', { | |
month: 'short', | |
day: 'numeric', | |
hour: '2-digit', | |
minute: '2-digit' | |
}); | |
} catch (e) { | |
console.error('Error formatting short date:', e); | |
return ''; | |
} | |
} | |
function formatDateTimeLocal(dateString) { | |
try { | |
const date = new Date(dateString); | |
if (isNaN(date.getTime())) { | |
return ''; | |
} | |
const year = date.getFullYear(); | |
const month = String(date.getMonth() + 1).padStart(2, '0'); | |
const day = String(date.getDate()).padStart(2, '0'); | |
const hours = String(date.getHours()).padStart(2, '0'); | |
const minutes = String(date.getMinutes()).padStart(2, '0'); | |
return `${year}-${month}-${day}T${hours}:${minutes}`; | |
} catch (e) { | |
console.error('Error formatting datetime-local:', e); | |
return ''; | |
} | |
} | |
}); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=jrrade/myworkouts" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> | |
</html> |