Spaces:
Running
Running
File size: 45,310 Bytes
ddd7255 0b93e65 840f62b 0b93e65 5af9065 0b93e65 ddd7255 0b93e65 ddd7255 0b93e65 33ac953 0b93e65 013977a 0b93e65 5af9065 ddd7255 aec3430 7e54a3e 26da13c 0b93e65 013977a 5af9065 ee1053c 0b93e65 ee1053c f3abe96 0b93e65 26da13c f3abe96 0b93e65 33ac953 aec3430 0b93e65 aec3430 ee1053c f3abe96 ee1053c f3abe96 33ac953 0b93e65 26da13c 41ebeb8 ddd7255 26da13c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LiftTrack - Firebase Edition v4</title> <!-- Nom mis à jour -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>
<!-- SDK Firebase (v8) -->
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
<style>
/* CSS Identique à la version fonctionnelle précédente */
:root { /* ... */ --bg-dark: #121212; --bg-card: #1e1e1e; --text-light: #e0e0e0; --accent: #4CAF50; --accent-dark: #3a8a3d; --danger: #f44336; --info: #2196F3; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background-color: var(--bg-dark); color: var(--text-light); min-height: 100vh; padding-bottom: 80px; } .container { width: 100%; max-width: 800px; margin: 0 auto; padding: 1rem; } header { padding: 1rem 0; text-align: center; border-bottom: 1px solid #333; margin-bottom: 1rem; } h1, h2, h3 { color: var(--accent); } .btn { background-color: var(--accent); color: white; border: none; padding: 0.6rem 1.2rem; border-radius: 4px; cursor: pointer; font-weight: bold; transition: background-color 0.2s; } .btn:hover { background-color: var(--accent-dark); } .btn:disabled { background-color: #555; cursor: not-allowed; } .btn-outline { background-color: transparent; color: var(--accent); border: 1px solid var(--accent); } .btn-outline:disabled { color: #555; border-color: #555; } .btn-danger { background-color: var(--danger); } .btn-info { background-color: var(--info); } input, select, textarea { width: 100%; padding: 0.6rem; margin-bottom: 1rem; background-color: #2a2a2a; border: 1px solid #444; border-radius: 4px; color: var(--text-light); } input[type="checkbox"] { width: auto; margin-right: 0.5rem; vertical-align: middle; } .card { background-color: var(--bg-card); border-radius: 8px; padding: 1rem; margin-bottom: 1rem; box-shadow: 0 2px 5px rgba(0,0,0,0.2); } .form-group { margin-bottom: 1rem; } .form-row { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; } .form-row > * { flex: 1; margin-bottom: 0; min-width: 100px; } label { display: block; margin-bottom: 0.3rem; color: #bbb; font-size: 0.9rem; } .exercise { border-left: 3px solid var(--accent); padding-left: 1rem; margin-bottom: 1.5rem; display: none; } .exercise.active-exercise { display: block; animation: fadeIn 0.3s ease-in-out; } .exercise-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; gap: 0.5rem;} .exercise-header input {margin-bottom: 0;} .series-container { margin-left: 0.5rem; margin-top: 0.5rem; } .series { background-color: #252525; padding: 0.7rem; border-radius: 4px; margin-bottom: 0.5rem; } .nav-bottom { position: fixed; bottom: 0; left: 0; width: 100%; background-color: #1a1a1a; display: flex; justify-content: space-around; padding: 0.7rem 0; box-shadow: 0 -2px 10px rgba(0,0,0,0.3); z-index: 10; } .nav-item { text-align: center; color: #888; text-decoration: none; font-size: 0.85rem; transition: color 0.2s; padding: 0 0.5rem;} .nav-item.active { color: var(--accent); } .nav-icon { font-size: 1.4rem; margin-bottom: 0.2rem; } .workout-card { border-left: 3px solid var(--accent); cursor: pointer; transition: transform 0.2s; } .workout-card:hover { transform: translateX(5px); } .workout-header { display: flex; justify-content: space-between; align-items: flex-start; } .stat-card { text-align: center; padding: 1rem; } .stat-value { font-size: 1.8rem; color: var(--accent); font-weight: bold; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 1rem; } .hidden { display: none; } #login-page { display: block; } #main-app-content { display: none; } #app-container > div:not(.active) { display: none !important; } .flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;} .badge { background-color: var(--accent); color: white; padding: 0.2rem 0.5rem; border-radius: 10px; font-size: 0.8rem; white-space: nowrap; margin-left: 5px; } .badge.type-badge { background-color: var(--info); } .spinner { border: 4px solid rgba(0, 0, 0, 0.1); width: 36px; height: 36px; border-radius: 50%; border-left-color: var(--accent); animation: spin 1s linear infinite; margin: 2rem auto; display: none; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .exercise-summary { margin: 0.3rem 0; padding: 0.3rem 0; border-bottom: 1px solid #333; font-size: 0.9rem;} .exercise-summary:last-child { border-bottom: none; } .satisfaction { display: flex; align-items: center; justify-content: center; flex-direction: column; margin-top: 1rem; } .satisfaction-value { font-size: 2rem; color: var(--accent); margin-top: 0.5rem; } .exercise-navigation { display: flex; justify-content: space-between; margin-top: 1rem; margin-bottom: 1rem; } #login-page .card { max-width: 400px; margin: 2rem auto; } #login-page h2 { text-align: center; margin-bottom: 1.5rem; } #login-error { color: var(--danger); text-align: center; margin-top: 0.5rem; font-size: 0.9rem; display: none; min-height: 1.2em; } #login-form button { margin-top: 0.5rem;} .auth-switch { text-align: center; margin-top: 1.5rem; font-size: 0.9rem; } .auth-switch a { color: var(--accent); cursor: pointer; text-decoration: underline; } .user-info { text-align: right; margin-bottom: 1rem; font-size: 0.9rem; color: #bbb;} .user-info strong { color: var(--text-light); } .user-info button { margin-left: 0.5rem; padding: 0.2rem 0.5rem; font-size: 0.8rem; } #workout-types-list li { display: flex; justify-content: space-between; align-items: center; background-color: #2a2a2a; padding: 0.5rem 0.8rem; margin-bottom: 0.5rem; border-radius: 4px; border-left: 3px solid var(--info);} #add-type-form { display: flex; gap: 0.5rem; align-items: flex-end;} #add-type-form input { margin-bottom: 0; flex-grow: 1;} #add-type-form button { flex-shrink: 0; } #select-workout-type-page .card { text-align: center; } #select-workout-type-page .btn { margin-top: 1rem; } #select-workout-type { margin-bottom: 1rem;} @media (max-width: 600px) { .form-row { flex-direction: column; gap: 0; } .container { padding: 0.5rem; } h1 { font-size: 1.5rem; } .flex-between { flex-direction: column; align-items: stretch; } .flex-between > div { width: 100%; display: flex; justify-content: flex-end; margin-top: 0.5rem;} .user-info { text-align: center; margin-bottom: 0.5rem;} .user-info button { display: block; margin: 0.5rem auto 0;} .exercise-navigation button { padding: 0.5rem; font-size: 0.9rem;} #add-type-form { flex-direction: column; align-items: stretch;} } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
</style>
</head>
<body>
<!-- HTML Body (avec les pages pour les types) -->
<div class="container"> <header> <h1>LiftTrack</h1> <p>Suivi et Analyse de Séances</p> </header> <div id="login-page"> <div class="card"> <h2 id="auth-title">Connexion</h2> <form id="login-form"> <div class="form-group"> <label for="auth-email">Email</label> <input type="email" id="auth-email" placeholder="[email protected]" required> </div> <div class="form-group"> <label for="auth-password">Mot de passe</label> <input type="password" id="auth-password" placeholder="********" required> </div> <p id="login-error"></p> <button type="submit" id="auth-action-btn" class="btn" style="width: 100%;">Se Connecter</button> </form> <div class="auth-switch"> <span id="auth-switch-text">Pas encore de compte ?</span> <a id="auth-switch-link">Inscrivez-vous ici</a> </div> </div> </div> <div id="main-app-content"> <div class="user-info"> Connecté: <strong id="current-user-display"></strong> <button id="logout-btn" class="btn btn-outline">Déconnexion</button> </div> <div id="app-container"> <div id="home-page" class="active"> <div class="flex-between"> <h2>Mes séances</h2> <div> <button id="manage-types-btn" class="btn btn-outline" style="margin-right: 0.5rem;">Gérer Types</button> <button id="new-workout-btn" class="btn">Nouvelle séance</button> </div> </div> <div id="workouts-list" class="workout-list" style="margin-top: 1rem;"> <div class="spinner hidden"></div> <p id="empty-workout-message" class="hidden" style="text-align: center; margin-top: 2rem;"> Aucune séance enregistrée. </p> </div> </div> <div id="manage-types-page"> <div class="flex-between"> <h2>Gérer les Types</h2> <button class="btn btn-outline back-to-home-btn">Retour</button> </div> <div class="card"> <h3>Types Existants</h3> <ul id="workout-types-list" style="list-style: none; padding: 0; margin-top: 1rem;"> <li class="hidden" id="empty-types-message">Aucun type défini.</li> </ul> <div class="spinner hidden" id="types-spinner"></div> </div> <div class="card"> <h3>Ajouter un Type</h3> <form id="add-type-form"> <input type="text" id="new-type-name" placeholder="Nom (ex: Dos, Jambes)" required> <button type="submit" class="btn">Ajouter</button> </form> <p id="add-type-error" style="color: var(--danger); font-size: 0.9rem; margin-top: 0.5rem;"></p> </div> </div> <div id="select-workout-type-page"> <div class="flex-between"> <h2>Démarrer Séance</h2> <button class="btn btn-outline back-to-home-btn">Annuler</button> </div> <div class="card"> <h3>Type Prédéfini</h3> <select id="select-workout-type"> <option value="">-- Sélectionner --</option> </select> <button id="start-structured-workout-btn" class="btn" disabled>Démarrer Structurée</button> </div> <div class="card"> <h3>Séance Libre</h3> <button id="start-free-workout-btn" class="btn btn-info">Démarrer Libre</button> </div> </div> <div id="new-workout-page"> <div class="flex-between"> <h2>Nouvelle séance <span id="workout-type-indicator" class="badge type-badge hidden"></span> <span id="current-exercise-indicator" style="font-size: 0.9rem; color: #ccc;"></span></h2> <div> <button id="cancel-new-workout-btn" class="btn btn-outline" style="margin-right: 0.5rem;">Annuler</button> <button id="save-workout-btn" class="btn">Enregistrer</button> </div> </div> <div class="card"> <div class="form-group"> <label for="workout-name">Nom Séance (optionnel)</label> <input type="text" id="workout-name" placeholder="Ex: Push Intense..."> </div> <div class="form-row"> <div class="form-group"> <label for="workout-date">Date</label> <input type="date" id="workout-date"> </div> <div class="form-group"> <label for="workout-duration">Durée (min)</label> <input type="number" id="workout-duration" min="1" placeholder="60"> </div> </div> </div> <h3 style="margin: 1rem 0;">Exercices</h3> <div class="exercise-navigation card"> <button id="prev-exercise-btn" class="btn btn-outline">< Précédent</button> <span style="align-self: center; color: #ccc;">Exercice Actif</span> <button id="next-exercise-btn" class="btn btn-outline">Suivant ></button> </div> <div id="exercises-container"> </div> <button id="add-exercise-btn" class="btn btn-outline" style="width: 100%; margin-top: 1rem;">+ Ajouter Exercice</button> <div class="card" style="margin-top: 2rem;"> <div class="form-group"> <label for="satisfaction">Satisfaction (1-100%)</label> <input type="range" id="satisfaction" min="1" max="100" value="75"> <div class="satisfaction"> <span></span> <div class="satisfaction-value">75%</div> </div> </div> </div> </div> <div id="workout-details-page"> <div class="flex-between"> <h2>Détail Séance <span id="detail-workout-type" class="badge type-badge hidden"></span></h2> <button class="btn btn-outline back-to-home-btn">Retour</button> </div> <div class="card"> <div class="workout-details-info"> <p><strong>Nom:</strong> <span id="detail-workout-name"></span></p> <div class="form-row"> <p><strong>Date:</strong> <span id="detail-date"></span></p> <p><strong>Durée:</strong> <span id="detail-duration"></span> min</p> </div> </div> </div> <div class="stats-grid" style="margin-top: 1rem;"> <div class="card stat-card"> <div class="stat-value" id="detail-tonnage">0</div> <div>Tonnage (kg)</div> </div> <div class="card stat-card"> <div class="stat-value" id="detail-satisfaction">0%</div> <div>Satisfaction</div> </div> <div class="card stat-card"> <div class="stat-value" id="detail-exercises-count">0</div> <div>Exercices</div> </div> </div> <h3 style="margin: 1.5rem 0 1rem;">Exercices Réalisés</h3> <div id="detail-exercises-container"> </div> <button id="delete-workout-btn" class="btn btn-danger" style="width: 100%; margin-top: 2rem;">Supprimer</button> </div>
<!-- Page Statistiques (Placeholder simple) -->
<div id="stats-page"> <h2>Statistiques</h2> <div class="stats-grid"> <div class="card stat-card"> <div class="stat-value" id="stats-workout-count">0</div> <div>Séances Totales</div> </div> <div class="card stat-card"> <div class="stat-value" id="stats-avg-tonnage">0</div> <div>Tonnage Moyen</div> </div> <div class="card stat-card"> <div class="stat-value" id="stats-avg-satisfaction">0%</div> <div>Satisfaction Moy.</div> </div> </div> <h3 style="margin: 1.5rem 0 1rem;">Tendances par Type</h3> <div class="card"> <p style="text-align: center; margin: 1rem 0; color: #888;"> (Fonctionnalité non implémentée.) </p> </div> </div> </div>
<nav class="nav-bottom"> <a href="#" class="nav-item active" data-page="home-page"> <div class="nav-icon">📋</div> <div>Séances</div> </a> <a href="#" class="nav-item" data-page="stats-page"> <div class="nav-icon">📊</div> <div>Stats</div> </a> </nav> </div>
<script> // Début du script principal
console.log("Script démarré.");
// =============== AJOUT FIREBASE ===============
const firebaseConfig = { apiKey: "AIzaSyAkWvrRyXgrC7zbTtoh_GppsHMrz2rF7WM", authDomain: "lifttrackapp.firebaseapp.com", projectId: "lifttrackapp", storageBucket: "lifttrackapp.appspot.com", messagingSenderId: "594426771796", appId: "1:594426771796:web:789bef037ca0016c54b0c1", measurementId: "G-MXLFK0H160" };
let app, auth, db;
try {
app = firebase.initializeApp(firebaseConfig);
auth = firebase.auth();
db = firebase.firestore();
console.log("Firebase Initialisé !");
} catch (e) {
console.error("Erreur Initialisation Firebase:", e);
alert("Impossible d'initialiser la connexion à la base de données.");
}
// =============== FIN AJOUT FIREBASE ===============
// --- Variables d'État ---
let workouts = []; let currentWorkoutId = null; let currentExerciseIndex = 0; let workoutExercisesForm = []; let currentFirebaseUser = null; let userWorkoutTypes = []; let selectedWorkoutTypeName = null;
// --- Éléments DOM ---
const loginPage = document.getElementById('login-page'); const mainAppContent = document.getElementById('main-app-content'); const authEmailInput = document.getElementById('auth-email'); const authPasswordInput = document.getElementById('auth-password'); const loginForm = document.getElementById('login-form'); const authActionButton = document.getElementById('auth-action-btn'); const authSwitchLink = document.getElementById('auth-switch-link'); const authTitle = document.getElementById('auth-title'); const authSwitchText = document.getElementById('auth-switch-text'); const loginError = document.getElementById('login-error'); const currentUserDisplay = document.getElementById('current-user-display'); const logoutBtn = document.getElementById('logout-btn'); const spinner = document.querySelector('#workouts-list .spinner'); const appContainer = document.getElementById('app-container'); const navItems = document.querySelectorAll('.nav-item'); const newWorkoutBtn = document.getElementById('new-workout-btn'); const saveWorkoutBtn = document.getElementById('save-workout-btn'); const cancelNewWorkoutBtn = document.getElementById('cancel-new-workout-btn'); const addExerciseBtn = document.getElementById('add-exercise-btn'); const exercisesContainer = document.getElementById('exercises-container'); const workoutsList = document.getElementById('workouts-list'); const backToHomeBtns = document.querySelectorAll('.back-to-home-btn'); const deleteWorkoutBtn = document.getElementById('delete-workout-btn'); const satisfactionRange = document.getElementById('satisfaction'); const satisfactionValue = document.querySelector('.satisfaction-value'); const emptyWorkoutMessage = document.getElementById('empty-workout-message'); const workoutDateInput = document.getElementById('workout-date'); const prevExerciseBtn = document.getElementById('prev-exercise-btn'); const nextExerciseBtn = document.getElementById('next-exercise-btn'); const currentExerciseIndicator = document.getElementById('current-exercise-indicator'); const workoutTypeIndicator = document.getElementById('workout-type-indicator'); const detailWorkoutType = document.getElementById('detail-workout-type'); const manageTypesBtn = document.getElementById('manage-types-btn'); const manageTypesPage = document.getElementById('manage-types-page'); const workoutTypesList = document.getElementById('workout-types-list'); const emptyTypesMessage = document.getElementById('empty-types-message'); const typesSpinner = document.getElementById('types-spinner'); const addTypeForm = document.getElementById('add-type-form'); const newTypeNameInput = document.getElementById('new-type-name'); const addTypeError = document.getElementById('add-type-error'); const selectWorkoutTypePage = document.getElementById('select-workout-type-page'); const selectWorkoutTypeDropdown = document.getElementById('select-workout-type'); const startStructuredWorkoutBtn = document.getElementById('start-structured-workout-btn'); const startFreeWorkoutBtn = document.getElementById('start-free-workout-btn');
// Éléments pour Stats Page (juste les valeurs globales)
const statsWorkoutCountEl = document.getElementById('stats-workout-count');
const statsAvgTonnageEl = document.getElementById('stats-avg-tonnage');
const statsAvgSatisfactionEl = document.getElementById('stats-avg-satisfaction');
let isLoginMode = true;
// ===========================================
// --- DÉFINITION DE TOUTES LES FONCTIONS ---
// ===========================================
function initAuthListener() { if (!auth) return; auth.onAuthStateChanged(user => { console.log("Auth state changed:", user ? user.uid : 'null'); if (user) { currentFirebaseUser = user; showApp(); } else { currentFirebaseUser = null; workouts = []; userWorkoutTypes = []; renderWorkoutsList(); renderWorkoutTypesList(); showLoginPage(); } }); }
function showLoginPage() { if(loginPage) loginPage.style.display = 'block'; if(mainAppContent) mainAppContent.style.display = 'none'; }
function showApp() { if (!currentFirebaseUser || !mainAppContent) return; console.log("Affichage App"); if(loginPage) loginPage.style.display = 'none'; mainAppContent.style.display = 'block'; if(currentUserDisplay) currentUserDisplay.textContent = currentFirebaseUser.email; setTodayDate(); loadWorkouts(); loadWorkoutTypes(); showPage('home-page'); }
function handleAuthAction(event) { if(!auth) return; event.preventDefault(); const email = authEmailInput.value; const password = authPasswordInput.value; loginError.textContent = ''; if (!email || !password) { loginError.textContent = 'Email/Pass requis.'; return; } authActionButton.disabled = true; authActionButton.textContent = 'Chargement...'; if (isLoginMode) { auth.signInWithEmailAndPassword(email, password).catch(handleAuthError).finally(() => { authActionButton.disabled = false; authActionButton.textContent = 'Se Connecter'; }); } else { auth.createUserWithEmailAndPassword(email, password).catch(handleAuthError).finally(() => { authActionButton.disabled = false; authSwitchMode(); authActionButton.textContent = 'S\'inscrire'; }); } }
function handleLogout() { if(!auth) return; console.log("Déconnexion..."); auth.signOut().catch(handleAuthError); }
function authSwitchMode() { isLoginMode = !isLoginMode; loginError.textContent = ''; authTitle.textContent=isLoginMode?'Connexion':'Inscription'; authActionButton.textContent=isLoginMode?'Se Connecter':'S\'inscrire'; authSwitchText.textContent=isLoginMode?'Pas de compte ?':'Déjà un compte ?'; authSwitchLink.textContent=isLoginMode?'Inscrivez-vous':'Connectez-vous'; }
function handleAuthError(error) { console.error("Erreur Auth:", error); loginError.textContent = getAuthErrorMessage(error); }
function getAuthErrorMessage(error) { switch (error.code) { case 'auth/invalid-email': return 'Email invalide.'; case 'auth/user-disabled': return 'Compte désactivé.'; case 'auth/user-not-found': return 'Utilisateur inconnu.'; case 'auth/wrong-password': return 'Mot de passe incorrect.'; case 'auth/email-already-in-use': return 'Email déjà utilisé.'; case 'auth/weak-password': return 'Mot de passe trop faible.'; default: return 'Erreur authentification.'; } }
function loadWorkoutTypes() { if (!currentFirebaseUser || !db) return; const userId = currentFirebaseUser.uid; console.log("Chargement types..."); userWorkoutTypes = []; if (typesSpinner) typesSpinner.classList.remove('hidden'); db.collection('workoutTypes').where('userId', '==', userId).orderBy('name').get().then(snapshot => { snapshot.forEach(doc => userWorkoutTypes.push({ id: doc.id, ...doc.data() })); console.log("Types chargés:", userWorkoutTypes); renderWorkoutTypesList(); populateWorkoutTypeSelector(); }).catch(handleFirestoreError).finally(() => { if (typesSpinner) typesSpinner.classList.add('hidden'); }); }
function renderWorkoutTypesList() { if (!workoutTypesList) return; workoutTypesList.innerHTML = ''; if (userWorkoutTypes.length === 0) { if(emptyTypesMessage) emptyTypesMessage.classList.remove('hidden'); } else { if(emptyTypesMessage) emptyTypesMessage.classList.add('hidden'); userWorkoutTypes.forEach(type => { const li = document.createElement('li'); li.textContent = type.name; workoutTypesList.appendChild(li); }); } }
function handleAddWorkoutType(event) { event.preventDefault(); if (!currentFirebaseUser || !newTypeNameInput || !db) return; const typeName = newTypeNameInput.value.trim(); addTypeError.textContent = ''; if (!typeName) { addTypeError.textContent = "Nom vide."; return; } if (userWorkoutTypes.some(t => t.name.toLowerCase() === typeName.toLowerCase())) { addTypeError.textContent = "Type existe déjà."; return; } console.log("Ajout type:", typeName); const typeData = { userId: currentFirebaseUser.uid, name: typeName }; db.collection('workoutTypes').add(typeData).then(() => { console.log("Type ajouté"); newTypeNameInput.value = ''; loadWorkoutTypes(); }).catch(handleFirestoreError); }
function populateWorkoutTypeSelector() { if (!selectWorkoutTypeDropdown) return; selectWorkoutTypeDropdown.innerHTML = '<option value="">-- Sélectionner --</option>'; userWorkoutTypes.forEach(type => { const option = document.createElement('option'); option.value = type.name; option.textContent = type.name; selectWorkoutTypeDropdown.appendChild(option); }); updateStartStructuredBtnState(); }
function updateStartStructuredBtnState() { if (!selectWorkoutTypeDropdown || !startStructuredWorkoutBtn) return; startStructuredWorkoutBtn.disabled = !selectWorkoutTypeDropdown.value; }
function loadWorkouts() { if (!currentFirebaseUser || !db) { console.log("Chargement séances annulé."); workouts = []; renderWorkoutsList(); showPage('home-page'); return; } const userId = currentFirebaseUser.uid; console.log(`Chargement séances ${userId}...`); if (spinner) spinner.classList.remove('hidden'); if(emptyWorkoutMessage) emptyWorkoutMessage.classList.add('hidden'); if(workoutsList) workoutsList.innerHTML = ''; workouts = []; db.collection('workouts').where('userId', '==', userId).orderBy('date', 'desc').get().then((querySnapshot) => { console.log(`${querySnapshot.size} séances trouvées.`); querySnapshot.forEach((doc) => { const workoutData = doc.data(); workoutData.firestoreId = doc.id; workouts.push(workoutData); }); renderWorkoutsList(); updateStats(); /* Mettre à jour stats après chargement */ showPage('home-page'); }).catch(handleFirestoreError).finally(() => { if (spinner) spinner.classList.add('hidden'); }); }
function saveWorkout() { if (!currentFirebaseUser || !db) { alert("Non connecté."); return; } const userId = currentFirebaseUser.uid; workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); const workoutNameInput = document.getElementById('workout-name'); const workoutName = workoutNameInput ? workoutNameInput.value.trim() : ''; const workoutDate = workoutDateInput.value; const workoutDuration = parseInt(document.getElementById('workout-duration').value) || 0; const satisfaction = parseInt(satisfactionRange.value); if (!selectedWorkoutTypeName) { alert("Type séance non défini."); return; } if (!workoutDate || workoutDuration <= 0) { alert("Données invalides."); return; } const exerciseElements = workoutExercisesForm; const exercises = []; if (exerciseElements.length === 0) { alert("Ajoutez exercices."); return; } let validationError = null; exerciseElements.forEach((exerciseEl, index) => { if (validationError) return; const nameInput = exerciseEl.querySelector('.exercise-name'); const exerciseName = nameInput ? nameInput.value.trim() : ''; const isUnilateral = exerciseEl.querySelector('.unilateral-checkbox').checked; if (!exerciseName) { validationError = `Nommez exo #${index + 1}.`; if(nameInput) nameInput.focus(); return; } const series = []; const seriesElements = exerciseEl.querySelectorAll('.series'); if (seriesElements.length === 0) { validationError = `"${exerciseName}" sans série.`; return; } seriesElements.forEach(seriesEl => { if (validationError) return; const repsInput = seriesEl.querySelector('.reps'); const weightInput = seriesEl.querySelector('.weight'); const reps = parseInt(repsInput.value) || 0; const weight = parseFloat(weightInput.value) || 0; const isDegressive = seriesEl.querySelector('.degressive-checkbox').checked; if (reps <= 0) { validationError = `Reps invalides: "${exerciseName}".`; if(repsInput) repsInput.focus(); return; } if (weight < 0) { validationError = `Charge invalide: "${exerciseName}".`; if(weightInput) weightInput.focus(); return; } series.push({ reps, weight, isDegressive }); }); if (!validationError) { exercises.push({ name: exerciseName, isUnilateral, series }); } }); if (validationError) { alert(validationError); return; } let totalTonnage = 0; exercises.forEach(ex => ex.series.forEach(s => totalTonnage += s.reps * s.weight * (ex.isUnilateral ? 2 : 1))); const finalWorkoutName = workoutName || selectedWorkoutTypeName; const workout = { id: currentWorkoutId || `workout-${Date.now()}`, userId: userId, workoutTypeName: selectedWorkoutTypeName, name: finalWorkoutName, date: workoutDate, duration: workoutDuration, exercises: exercises, totalTonnage: totalTonnage, satisfaction: satisfaction }; console.log("Sauvegarde:", workout); saveWorkoutBtn.disabled = true; saveWorkoutBtn.textContent = 'Sauvegarde...'; db.collection('workouts').doc(workout.id).set(workout).then(() => { console.log("Sauvegardé:", workout.id); const existingIndex = workouts.findIndex(w => w.id === workout.id); if (existingIndex > -1) workouts[existingIndex] = workout; else workouts.push(workout); confetti({ particleCount: 150, spread: 90, origin: { y: 0.6 } }); showPage('home-page'); renderWorkoutsList(); }).catch(handleFirestoreError).finally(() => { saveWorkoutBtn.disabled = false; saveWorkoutBtn.textContent = 'Enregistrer Séance'; }); }
function deleteWorkout() { if (!currentFirebaseUser || !currentWorkoutId || !db) return; const workoutToDelete = workouts.find(w => w.id === currentWorkoutId); if (!workoutToDelete) return; const confirmDelete = confirm(`Supprimer "${workoutToDelete.name}"?`); if (!confirmDelete) return; console.log("Suppression:", currentWorkoutId); deleteWorkoutBtn.disabled = true; deleteWorkoutBtn.textContent = 'Suppression...'; db.collection('workouts').doc(currentWorkoutId).delete().then(() => { console.log("Supprimé:", currentWorkoutId); workouts = workouts.filter(w => w.id !== currentWorkoutId); currentWorkoutId = null; showPage('home-page'); renderWorkoutsList(); }).catch(handleFirestoreError).finally(() => { deleteWorkoutBtn.disabled = false; deleteWorkoutBtn.textContent = 'Supprimer'; }); }
function setTodayDate() { if(workoutDateInput) try { workoutDateInput.value = new Date().toISOString().split('T')[0]; } catch(e){ console.error("Erreur date:", e); }}
function showPage(pageId) { console.log(`Affichage page: ${pageId}`); if (!currentFirebaseUser && pageId !== 'login-page') { showLoginPage(); return; } document.querySelectorAll('#app-container > div').forEach(page => page.classList.remove('active')); const pageToShow = document.getElementById(pageId); if (pageToShow) { pageToShow.classList.add('active'); if (pageId === 'new-workout-page') { triggerFlameAnimation(); renderActiveExerciseForm(); updateWorkoutTypeIndicator(); } if (pageId === 'manage-types-page') { loadWorkoutTypes(); } if (pageId === 'select-workout-type-page') { populateWorkoutTypeSelector(); } if (pageId === 'stats-page') { updateStats(); } } else { console.error(`Page ID "${pageId}" non trouvée.`); document.getElementById('home-page').classList.add('active'); pageId = 'home-page'; } navItems.forEach(item => { item.classList.remove('active'); }); const activeNavItem = document.querySelector(`.nav-item[data-page="${pageId === 'stats-page' ? 'stats-page' : 'home-page'}"]`); if(activeNavItem) activeNavItem.classList.add('active'); }
function triggerFlameAnimation() { const duration = 1 * 1000; const animationEnd = Date.now() + duration; const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 }; function randomInRange(min, max) { return Math.random() * (max - min) + min; } const interval = setInterval(function() { const timeLeft = animationEnd - Date.now(); if (timeLeft <= 0) return clearInterval(interval); const particleCount = 50 * (timeLeft / duration); confetti(Object.assign({}, defaults, { particleCount, origin: { x: randomInRange(0.3, 0.7), y: Math.random() - 0.2 }, angle: randomInRange(240, 300), spread: randomInRange(50, 90), gravity: 0.5, drift: randomInRange(-1, 1), colors: ['#ff6f00', '#ff8f00', '#ffa000', '#ffcc00'] })); }, 250); }
function handleAddNewExercise() { addExercise(true); }
function addExercise(navigateToNew = false) { if (!currentFirebaseUser) return; const exerciseId = `exercise-${Date.now()}`; const exerciseDiv = document.createElement('div'); exerciseDiv.className = 'card exercise'; exerciseDiv.setAttribute('data-exercise-id', exerciseId); exerciseDiv.innerHTML = `<div class="exercise-header"> <input type="text" placeholder="Nom Exercice" class="exercise-name"> <button class="btn btn-danger remove-exercise" style="padding: 0.3rem 0.6rem; flex-shrink: 0;">×</button> </div> <div class="form-group" style="margin-top: 0.5rem; margin-bottom: 0.5rem;"> <label style="display: inline-flex; align-items: center; color: #bbb; font-size: 0.9rem;"> <input type="checkbox" class="unilateral-checkbox"> Unilatéral </label> </div> <div class="series-container"></div> <button class="btn btn-outline add-series" style="width: 100%; margin-top: 0.5rem; padding: 0.4rem;">+ Ajouter Série</button>`; exercisesContainer.appendChild(exerciseDiv); workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); const removeBtn = exerciseDiv.querySelector('.remove-exercise'); removeBtn.addEventListener('click', () => { const indexToRemove = workoutExercisesForm.indexOf(exerciseDiv); if (indexToRemove > -1) { exerciseDiv.remove(); workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); if (currentExerciseIndex >= indexToRemove) currentExerciseIndex = Math.max(0, currentExerciseIndex - 1); if (currentExerciseIndex >= workoutExercisesForm.length) currentExerciseIndex = Math.max(0, workoutExercisesForm.length - 1); renderActiveExerciseForm(); } }); const addSeriesBtn = exerciseDiv.querySelector('.add-series'); const seriesContainer = exerciseDiv.querySelector('.series-container'); addSeriesBtn.addEventListener('click', () => addSeries(seriesContainer)); addSeries(seriesContainer); if (navigateToNew) { currentExerciseIndex = workoutExercisesForm.length - 1; renderActiveExerciseForm(); } else updateExerciseNavButtons(); }
function addSeries(container) { if (!currentFirebaseUser || !container) return; const seriesId = `series-${Date.now()}`; const seriesDiv = document.createElement('div'); seriesDiv.className = 'series'; seriesDiv.setAttribute('data-series-id', seriesId); seriesDiv.innerHTML = `<div class="form-row" style="align-items: flex-end; gap: 0.8rem;"> <div class="form-group" style="flex: 1.2;"> <label>Reps</label> <input type="number" class="reps" min="1" placeholder="10" style="padding: 0.4rem;"> </div> <div class="form-group" style="flex: 1.2;"> <label>Charge (kg)</label> <input type="number" class="weight" min="0" step="0.1" placeholder="20" style="padding: 0.4rem;"> </div> <div class="form-group" style="flex: 1; display: flex; align-items: center; padding-bottom: 0.6rem; min-width: 100px;"> <label style="display: inline-flex; align-items: center; color: #bbb; font-size: 0.8rem; margin-bottom: 0; white-space: nowrap;"> <input type="checkbox" class="degressive-checkbox" style="margin-right: 0.3rem;"> Dégressive </label> </div> <button class="btn btn-danger remove-series" style="padding: 0.3rem 0.6rem; margin-bottom: 0.6rem; flex-basis: 30px; flex-grow: 0; align-self: center;">×</button> </div>`; container.appendChild(seriesDiv); const removeBtn = seriesDiv.querySelector('.remove-series'); removeBtn.addEventListener('click', () => seriesDiv.remove()); }
function navigateExerciseForm(direction) { workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); const newIndex = currentExerciseIndex + direction; if (newIndex >= 0 && newIndex < workoutExercisesForm.length) { currentExerciseIndex = newIndex; renderActiveExerciseForm(); } }
function renderActiveExerciseForm() { workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); workoutExercisesForm.forEach(ex => ex.classList.remove('active-exercise')); if (currentExerciseIndex >= 0 && currentExerciseIndex < workoutExercisesForm.length) workoutExercisesForm[currentExerciseIndex].classList.add('active-exercise'); updateExerciseNavButtons(); }
function updateExerciseNavButtons() { const totalExercises = workoutExercisesForm.length; if (totalExercises === 0) { currentExerciseIndicator.textContent = "(Aucun exercice)"; prevExerciseBtn.disabled = true; nextExerciseBtn.disabled = true; } else { currentExerciseIndicator.textContent = `(${currentExerciseIndex + 1}/${totalExercises})`; prevExerciseBtn.disabled = currentExerciseIndex === 0; nextExerciseBtn.disabled = currentExerciseIndex === totalExercises - 1; } }
function clearNewWorkoutForm(typeName = "Libre") { console.log("Nettoyage form pour type:", typeName); if (!currentFirebaseUser) return; selectedWorkoutTypeName = typeName; const workoutNameInput = document.getElementById('workout-name'); if(workoutNameInput) workoutNameInput.value = (typeName === "Libre" ? "" : typeName); if(workoutDateInput) setTodayDate(); if(document.getElementById('workout-duration')) document.getElementById('workout-duration').value = ''; if(exercisesContainer) exercisesContainer.innerHTML = ''; workoutExercisesForm = []; if(satisfactionRange) satisfactionRange.value = 75; if(satisfactionValue) satisfactionValue.textContent = '75%'; currentWorkoutId = null; currentExerciseIndex = 0; addExercise(false); updateWorkoutTypeIndicator(); renderActiveExerciseForm(); }
function updateWorkoutTypeIndicator() { if (!workoutTypeIndicator) return; if (selectedWorkoutTypeName && selectedWorkoutTypeName !== "Libre") { workoutTypeIndicator.textContent = selectedWorkoutTypeName; workoutTypeIndicator.classList.remove('hidden'); } else { workoutTypeIndicator.classList.add('hidden'); } }
function renderWorkoutsList() { if (!currentFirebaseUser) { workoutsList.innerHTML = ''; emptyWorkoutMessage.classList.remove('hidden'); updateStats(); return; } workoutsList.innerHTML = ''; if (workouts.length === 0) { emptyWorkoutMessage.classList.remove('hidden'); } else { emptyWorkoutMessage.classList.add('hidden'); const sortedWorkouts = [...workouts].sort((a, b) => new Date(b.date) - new Date(a.date)); sortedWorkouts.forEach(workout => { const workoutDate = new Date(workout.date).toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric' }); const workoutDiv = document.createElement('div'); workoutDiv.className = 'card workout-card'; workoutDiv.setAttribute('data-workout-id', workout.id); const typeBadge = workout.workoutTypeName && workout.workoutTypeName !== "Libre" ? `<span class="badge type-badge">${workout.workoutTypeName}</span>` : ''; workoutDiv.innerHTML = `<div class="workout-header"><h3 style="margin-bottom: 0.5rem;">${workout.name} ${typeBadge}</h3><div class="badge">${workoutDate}</div></div><div class="workout-details" style="font-size: 0.9rem; color: #ccc; margin-top: 0.5rem;"><span>${workout.duration} min</span> | <span>${workout.exercises.length} exo${workout.exercises.length > 1 ? 's' : ''}</span> | <span>${workout.totalTonnage.toFixed(1)} kg</span> | <span>${workout.satisfaction}%</span></div>`; workoutDiv.addEventListener('click', () => displayWorkoutDetails(workout.id)); workoutsList.appendChild(workoutDiv); }); } /* Ne pas appeler updateStats ici pour éviter boucle */ }
function displayWorkoutDetails(workoutId) { if (!currentFirebaseUser) return; const workout = workouts.find(w => w.id === workoutId); if (!workout) { showPage('home-page'); return; } if(detailWorkoutType) { if (workout.workoutTypeName && workout.workoutTypeName !== "Libre") { detailWorkoutType.textContent = workout.workoutTypeName; detailWorkoutType.classList.remove('hidden'); } else detailWorkoutType.classList.add('hidden'); } if(document.getElementById('detail-workout-name')) document.getElementById('detail-workout-name').textContent = workout.name; if(document.getElementById('detail-date')) document.getElementById('detail-date').textContent = new Date(workout.date).toLocaleDateString('fr-FR'); if(document.getElementById('detail-duration')) document.getElementById('detail-duration').textContent = workout.duration; if(document.getElementById('detail-tonnage')) document.getElementById('detail-tonnage').textContent = workout.totalTonnage.toFixed(1); if(document.getElementById('detail-satisfaction')) document.getElementById('detail-satisfaction').textContent = `${workout.satisfaction}%`; if(document.getElementById('detail-exercises-count')) document.getElementById('detail-exercises-count').textContent = workout.exercises.length; const detailExercisesContainer = document.getElementById('detail-exercises-container'); if(detailExercisesContainer) { detailExercisesContainer.innerHTML = ''; workout.exercises.forEach((exercise) => { const exerciseDiv = document.createElement('div'); exerciseDiv.className = 'card detail-exercise-card'; let seriesHtml = ''; exercise.series.forEach((serie, sIndex) => { const degressiveLabel = serie.isDegressive ? ' <span class="badge" style="font-size: 0.7rem; background-color: var(--accent-dark);">Dégr.</span>' : ''; const weightFactor = exercise.isUnilateral ? 2 : 1; const seriesTonnage = serie.reps * serie.weight * weightFactor; seriesHtml += `<div class="exercise-summary"><div class="flex-between"><span>Série ${sIndex + 1}: ${serie.reps} reps × ${serie.weight} kg${degressiveLabel}</span><span style="color: #aaa;">(${seriesTonnage.toFixed(1)} kg)</span></div></div>`; }); const unilateralLabel = exercise.isUnilateral ? ' <span class="badge" style="font-size: 0.7rem;">Unilat.</span>' : ''; exerciseDiv.innerHTML = `<h3 style="font-size: 1.1rem; margin-bottom: 0.5rem;">${exercise.name}${unilateralLabel}</h3> <div class="series-summary-container"> ${seriesHtml} </div>`; detailExercisesContainer.appendChild(exerciseDiv); }); } currentWorkoutId = workoutId; showPage('workout-details-page'); }
function updateStats() { /* ... (Version simple sans tendances) ... */ if (!currentFirebaseUser) return; const workoutCount = workouts.length; if (!statsWorkoutCountEl || !statsAvgTonnageEl || !statsAvgSatisfactionEl) return; statsWorkoutCountEl.textContent = workoutCount; if (workoutCount === 0) { statsAvgTonnageEl.textContent = '0'; statsAvgSatisfactionEl.textContent = '0%'; return; } const totalTonnageAll = workouts.reduce((sum, workout) => sum + (workout.totalTonnage || 0), 0); const avgTonnage = totalTonnageAll / workoutCount; statsAvgTonnageEl.textContent = avgTonnage.toFixed(1); const totalSatisfaction = workouts.reduce((sum, workout) => sum + (workout.satisfaction || 0), 0); const avgSatisfaction = totalSatisfaction / workoutCount; statsAvgSatisfactionEl.textContent = `${Math.round(avgSatisfaction)}%`; }
function handleFirestoreError(error) { console.error("Erreur Firestore:", error); alert("Erreur base de données."); }
// --- ÉCOUTEURS D'ÉVÉNEMENTS ---
function initEventListeners() {
console.log("initEventListeners: Attachement...");
if (loginForm) loginForm.addEventListener('submit', handleAuthAction); else console.error("!loginForm");
if (authSwitchLink) authSwitchLink.addEventListener('click', authSwitchMode); else console.error("!authSwitchLink");
if (logoutBtn) logoutBtn.addEventListener('click', handleLogout); else console.error("!logoutBtn");
navItems.forEach((item, index) => { if(item) item.addEventListener('click', (e) => { e.preventDefault(); if (!currentFirebaseUser) return; const targetPageId = item.getAttribute('data-page'); showPage(targetPageId); /* ... MAJ nav active ... */ }); else console.error(`!navItem #${index}`); });
if (newWorkoutBtn) newWorkoutBtn.addEventListener('click', () => { if (!currentFirebaseUser) return; showPage('select-workout-type-page'); }); else console.error("!newWorkoutBtn");
if (manageTypesBtn) manageTypesBtn.addEventListener('click', () => showPage('manage-types-page')); else console.error("!manageTypesBtn");
if (addTypeForm) addTypeForm.addEventListener('submit', handleAddWorkoutType); else console.error("!addTypeForm");
if (selectWorkoutTypeDropdown) selectWorkoutTypeDropdown.addEventListener('change', updateStartStructuredBtnState); else console.error("!selectWorkoutTypeDropdown");
if (startStructuredWorkoutBtn) startStructuredWorkoutBtn.addEventListener('click', () => { if(selectWorkoutTypeDropdown) { const type = selectWorkoutTypeDropdown.value; if(type) { clearNewWorkoutForm(type); showPage('new-workout-page'); } } }); else console.error("!startStructuredWorkoutBtn");
if (startFreeWorkoutBtn) startFreeWorkoutBtn.addEventListener('click', () => { clearNewWorkoutForm("Libre"); showPage('new-workout-page'); }); else console.error("!startFreeWorkoutBtn");
backToHomeBtns.forEach(btn => { if(btn) btn.addEventListener('click', () => showPage('home-page')); else console.error("!backToHomeBtn"); });
if (cancelNewWorkoutBtn) cancelNewWorkoutBtn.addEventListener('click', () => showPage('home-page')); else console.error("!cancelNewWorkoutBtn");
if (saveWorkoutBtn) saveWorkoutBtn.addEventListener('click', saveWorkout); else console.error("!saveWorkoutBtn");
if (addExerciseBtn) addExerciseBtn.addEventListener('click', handleAddNewExercise); else console.error("!addExerciseBtn");
if (prevExerciseBtn) prevExerciseBtn.addEventListener('click', navigateExerciseForm.bind(null, -1)); else console.error("!prevExerciseBtn");
if (nextExerciseBtn) nextExerciseBtn.addEventListener('click', navigateExerciseForm.bind(null, 1)); else console.error("!nextExerciseBtn");
if (deleteWorkoutBtn) deleteWorkoutBtn.addEventListener('click', deleteWorkout); else console.error("!deleteWorkoutBtn");
if (satisfactionRange) satisfactionRange.addEventListener('input', () => { if(satisfactionValue) satisfactionValue.textContent = `${satisfactionRange.value}%`; }); else console.error("!satisfactionRange");
console.log("initEventListeners: Fin attachement.");
}
// ==================================================
// --- FIN DES DÉFINITIONS DE FONCTIONS ---
// ==================================================
// --- INITIALISATION ---
if (auth) {
initAuthListener();
} else {
console.error("Firebase Auth non prêt!");
}
initEventListeners();
</script>
</body>
</html> |