BitDown commited on
Commit
aec3430
·
verified ·
1 Parent(s): 7e54a3e

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +81 -180
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>LiftTrack - Types de Séances</title> <!-- Titre mis à jour -->
7
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>
8
 
9
  <!-- SDK Firebase (v8) -->
@@ -12,204 +12,98 @@
12
  <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
13
 
14
  <style>
15
- /* CSS (légèrement adapté pour nouvelles pages/éléments) */
16
- :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; }
17
- .badge.type-badge { background-color: var(--info); } /* Badge pour type défini */
18
- .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; }
19
- #workout-types-list li { background-color: #2a2a2a; padding: 0.5rem 0.8rem; margin-bottom: 0.5rem; border-radius: 4px; border-left: 3px solid var(--info);}
20
- #add-type-form { display: flex; gap: 0.5rem; align-items: flex-end;}
21
- #add-type-form input { margin-bottom: 0; flex-grow: 1;}
22
- #add-type-form button { flex-shrink: 0; }
23
- #select-workout-type-page .card { text-align: center; }
24
- #select-workout-type-page .btn { margin-top: 1rem; }
25
- #select-workout-type { margin-bottom: 1rem;} /* Espace sous le dropdown */
26
- @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; } }
27
  </style>
28
  </head>
29
  <body>
30
- <div class="container">
31
- <header> <h1>LiftTrack</h1> <p>Suivi et Analyse de Séances</p> </header>
32
 
33
- <!-- Écran de Connexion/Inscription -->
34
- <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="votre@email.com" 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>
35
 
36
- <!-- Contenu Principal de l'Application -->
37
- <div id="main-app-content">
38
- <div class="user-info"> Connecté: <strong id="current-user-display"></strong> <button id="logout-btn" class="btn btn-outline">Déconnexion</button> </div>
39
-
40
- <div id="app-container">
41
- <!-- Page Accueil / Liste Séances -->
42
- <div id="home-page" class="active">
43
- <div class="flex-between">
44
- <h2>Mes séances</h2>
45
- <!-- Ajout bouton pour gérer les types -->
46
- <div>
47
- <button id="manage-types-btn" class="btn btn-outline" style="margin-right: 0.5rem;">Gérer Types</button>
48
- <button id="new-workout-btn" class="btn">Nouvelle séance</button>
49
- </div>
50
- </div>
51
- <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>
52
- </div>
53
-
54
- <!-- Nouvelle Page: Gérer les Types de Séances -->
55
- <div id="manage-types-page">
56
- <div class="flex-between">
57
- <h2>Gérer les Types de Séances</h2>
58
- <button class="btn btn-outline back-to-home-btn">Retour Accueil</button>
59
- </div>
60
- <div class="card">
61
- <h3>Types Existants</h3>
62
- <ul id="workout-types-list" style="list-style: none; padding: 0; margin-top: 1rem;">
63
- <!-- Types chargés ici -->
64
- <li class="hidden" id="empty-types-message">Aucun type défini.</li>
65
- </ul>
66
- <div class="spinner hidden" id="types-spinner"></div>
67
- </div>
68
- <div class="card">
69
- <h3>Ajouter un Nouveau Type</h3>
70
- <form id="add-type-form">
71
- <input type="text" id="new-type-name" placeholder="Nom du type (ex: Dos, Jambes)" required>
72
- <button type="submit" class="btn">Ajouter</button>
73
- </form>
74
- <p id="add-type-error" style="color: var(--danger); font-size: 0.9rem; margin-top: 0.5rem;"></p>
75
- </div>
76
- </div>
77
-
78
- <!-- Nouvelle Page: Choisir Type de Séance -->
79
- <div id="select-workout-type-page">
80
- <div class="flex-between">
81
- <h2>Démarrer une Nouvelle Séance</h2>
82
- <button class="btn btn-outline back-to-home-btn">Annuler</button>
83
- </div>
84
- <div class="card">
85
- <h3>Choisir un Type Prédéfini</h3>
86
- <select id="select-workout-type">
87
- <option value="">-- Sélectionner un type --</option>
88
- <!-- Options chargées ici -->
89
- </select>
90
- <button id="start-structured-workout-btn" class="btn" disabled>Démarrer Séance Structurée</button>
91
- </div>
92
- <div class="card">
93
- <h3>Ou démarrer une séance libre</h3>
94
- <button id="start-free-workout-btn" class="btn btn-info">Démarrer Séance Libre</button>
95
- </div>
96
- </div>
97
-
98
- <!-- Page Nouvelle Séance (structure similaire) -->
99
- <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 Séance</button> </div> </div> <div class="card"> <div class="form-group"> <label for="workout-name">Nom de la séance (optionnel si type défini)</label> <input type="text" id="workout-name" placeholder="Ex: Push Intense, Séance du Lundi..."> </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 un Nouvel Exercice</button> <div class="card" style="margin-top: 2rem;"> <div class="form-group"> <label for="satisfaction">Niveau de satisfaction (1-100%)</label> <input type="range" id="satisfaction" min="1" max="100" value="75"> <div class="satisfaction"> <span>Satisfaction</span> <div class="satisfaction-value">75%</div> </div> </div> </div> </div>
100
-
101
- <!-- Page Détail Séance (Ajout badge type) -->
102
- <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 Total (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 cette séance</button> </div>
103
-
104
- <!-- Page Statistiques (Pas encore de logique de comparaison) -->
105
- <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 Moyenne</div> </div> </div> <h3 style="margin: 1.5rem 0 1rem;">Tendances et Comparaisons par Type</h3> <div class="card"> <p style="text-align: center; margin: 1rem 0; color: #888;"> (Fonctionnalité de comparaison par type non implémentée.) </p> </div> </div>
106
- </div>
107
- <!-- Menu Navigation Bas -->
108
- <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>
109
- </div>
110
-
111
- <script>
112
- // Firebase Init (Identique)
113
  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" };
114
- try { firebase.initializeApp(firebaseConfig); console.log("Firebase Initialisé !"); } catch (e) { console.error("Erreur Init Firebase:", e); alert("Erreur connexion base de données."); }
115
- const auth = firebase.auth(); const db = firebase.firestore();
 
 
 
 
 
 
 
 
 
 
116
 
117
  // --- Variables d'État ---
118
- let workouts = []; let currentWorkoutId = null; let currentExerciseIndex = 0; let workoutExercisesForm = []; let currentFirebaseUser = null;
119
- let userWorkoutTypes = []; // Nouvelle variable pour les types
120
- let selectedWorkoutTypeName = null; // Stocke le type choisi pour la nouvelle séance
121
-
122
- // --- Éléments DOM ---
123
- // (Ajout des nouveaux éléments)
124
- 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'); // Utiliser la classe pour plusieurs boutons retour
125
- 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'); // Badge type dans form
126
- const detailWorkoutType = document.getElementById('detail-workout-type'); // Badge type dans détail
127
- // Nouveaux éléments
128
- const manageTypesBtn = document.getElementById('manage-types-btn');
129
- const manageTypesPage = document.getElementById('manage-types-page');
130
- const workoutTypesList = document.getElementById('workout-types-list');
131
- const emptyTypesMessage = document.getElementById('empty-types-message');
132
- const typesSpinner = document.getElementById('types-spinner');
133
- const addTypeForm = document.getElementById('add-type-form');
134
- const newTypeNameInput = document.getElementById('new-type-name');
135
- const addTypeError = document.getElementById('add-type-error');
136
- const selectWorkoutTypePage = document.getElementById('select-workout-type-page');
137
- const selectWorkoutTypeDropdown = document.getElementById('select-workout-type');
138
- const startStructuredWorkoutBtn = document.getElementById('start-structured-workout-btn');
139
- const startFreeWorkoutBtn = document.getElementById('start-free-workout-btn');
140
 
 
 
141
  let isLoginMode = true;
142
 
143
  // ===========================================
144
- // --- Définition Fonctions (Ordre Logique) ---
145
  // ===========================================
146
 
147
- // --- Auth & Initialisation ---
148
- function initAuthListener() { auth.onAuthStateChanged(user => { console.log("Auth state changed:", user ? user.uid : 'null'); if (user) { currentFirebaseUser = user; userWorkoutTypes = []; /* Reset types on login */ showApp(); } else { currentFirebaseUser = null; workouts = []; userWorkoutTypes = []; renderWorkoutsList(); renderWorkoutTypesList(); showLoginPage(); } }); }
149
- function showLoginPage() { /* ... */ loginPage.style.display = 'block'; mainAppContent.style.display = 'none'; }
150
- function showApp() { /* ... */ if (!currentFirebaseUser) return; loginPage.style.display = 'none'; mainAppContent.style.display = 'block'; currentUserDisplay.textContent = currentFirebaseUser.email; setTodayDate(); loadWorkouts(); loadWorkoutTypes(); /* Charger les types au démarrage */ showPage('home-page'); }
151
- function handleAuthAction(event) { /* ... */ 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'; }); } }
152
- function handleLogout() { /* ... */ auth.signOut().catch(handleAuthError); }
153
- 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'; }
154
  function handleAuthError(error) { console.error("Erreur Auth:", error); loginError.textContent = getAuthErrorMessage(error); }
155
  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.'; } }
156
-
157
- // --- Gestion des Types de Séance ---
158
- function loadWorkoutTypes() { if (!currentFirebaseUser) return; const userId = currentFirebaseUser.uid; console.log("Chargement types..."); userWorkoutTypes = []; // Reset 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(); // Mettre à jour le dropdown aussi }) .catch(error => console.error("Erreur chargement types:", error)) .finally(() => { if (typesSpinner) typesSpinner.classList.add('hidden'); }); }
159
- function renderWorkoutTypesList() { if (!workoutTypesList) return; workoutTypesList.innerHTML = ''; // Vider 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; // Ajouter un bouton supprimer éventuellement ici workoutTypesList.appendChild(li); }); } }
160
- function handleAddWorkoutType(event) { event.preventDefault(); if (!currentFirebaseUser || !newTypeNameInput) return; const typeName = newTypeNameInput.value.trim(); addTypeError.textContent = ''; if (!typeName) { addTypeError.textContent = "Le nom du type ne peut pas être vide."; return; } // Vérifier si le type existe déjà (insensible à la casse) if (userWorkoutTypes.some(t => t.name.toLowerCase() === typeName.toLowerCase())) { addTypeError.textContent = "Ce 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é avec succès"); newTypeNameInput.value = ''; loadWorkoutTypes(); // Recharger la liste }) .catch(error => { console.error("Erreur ajout type:", error); addTypeError.textContent = "Erreur lors de l'ajout."; }); }
161
- function populateWorkoutTypeSelector() { if (!selectWorkoutTypeDropdown) return; selectWorkoutTypeDropdown.innerHTML = '<option value="">-- Sélectionner un type --</option>'; // Reset userWorkoutTypes.forEach(type => { const option = document.createElement('option'); option.value = type.name; option.textContent = type.name; selectWorkoutTypeDropdown.appendChild(option); }); // Activer/désactiver bouton démarrage structuré updateStartStructuredBtnState(); }
162
  function updateStartStructuredBtnState() { if (!selectWorkoutTypeDropdown || !startStructuredWorkoutBtn) return; startStructuredWorkoutBtn.disabled = !selectWorkoutTypeDropdown.value; }
163
-
164
-
165
- // --- Logique Application Principale ---
166
- function loadWorkouts() { /* ... (Firestore, identique) ... */ if (!currentFirebaseUser) { console.log("Chargement séances annulé."); workouts = []; renderWorkoutsList(); showPage('home-page'); return; } const userId = currentFirebaseUser.uid; console.log(`Chargement séances pour ${userId}...`); if (spinner) spinner.classList.remove('hidden'); emptyWorkoutMessage.classList.add('hidden'); workoutsList.innerHTML = ''; workouts = []; db.collection('workouts').where('userId', '==', userId).orderBy('date', 'desc').get() .then((querySnapshot) => { console.log(`Trouvé ${querySnapshot.size} séances.`); querySnapshot.forEach((doc) => { const workoutData = doc.data(); workoutData.firestoreId = doc.id; workouts.push(workoutData); }); renderWorkoutsList(); showPage('home-page'); }) .catch((error) => { console.error("Erreur chargement séances: ", error); alert("Erreur chargement séances."); renderWorkoutsList(); showPage('home-page'); }) .finally(() => { if (spinner) spinner.classList.add('hidden'); }); }
167
- function saveWorkout() { /* ... (Ajout workoutTypeName) ... */ if (!currentFirebaseUser) { 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() : ''; // Nom spécifique optionnel const workoutDate = workoutDateInput.value; const workoutDuration = parseInt(document.getElementById('workout-duration').value) || 0; const satisfaction = parseInt(satisfactionRange.value); if (!selectedWorkoutTypeName) { alert("Erreur: Type de séance non défini."); return; } if (!workoutDate || workoutDuration <= 0) { alert("Données séance invalides."); return; } const exerciseElements = workoutExercisesForm; const exercises = []; if (exerciseElements.length === 0) { alert("Ajoutez au moins un exercice."); return; } let validationError = null; /* ... Validation exercices identique ... */ 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 l'exercice #${index + 1}.`; if(nameInput) nameInput.focus(); return; } const series = []; const seriesElements = exerciseEl.querySelectorAll('.series'); if (seriesElements.length === 0) { validationError = `Exercice "${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 pour "${exerciseName}".`; if(repsInput) repsInput.focus(); return; } if (weight < 0) { validationError = `Charge invalide pour "${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; // Utilise le nom spécifique OU le nom du type const workout = { id: currentWorkoutId || `workout-${Date.now()}`, userId: userId, workoutTypeName: selectedWorkoutTypeName, // **Ajout du type** name: finalWorkoutName, // Nom final date: workoutDate, duration: workoutDuration, exercises: exercises, totalTonnage: totalTonnage, satisfaction: satisfaction }; console.log("Sauvegarde séance:", workout); saveWorkoutBtn.disabled = true; saveWorkoutBtn.textContent = 'Sauvegarde...'; db.collection('workouts').doc(workout.id).set(workout).then(() => { console.log("Séance sauvegardée:", 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'; }); }
168
- function deleteWorkout() { /* ... (Firestore, identique) ... */ if (!currentFirebaseUser || !currentWorkoutId) return; const workoutToDelete = workouts.find(w => w.id === currentWorkoutId); if (!workoutToDelete) return; const confirmDelete = confirm(`Supprimer séance "${workoutToDelete.name}" du ${new Date(workoutToDelete.date).toLocaleDateString('fr-FR')} ?`); if (!confirmDelete) return; console.log("Suppression séance:", currentWorkoutId); deleteWorkoutBtn.disabled = true; deleteWorkoutBtn.textContent = 'Suppression...'; db.collection('workouts').doc(currentWorkoutId).delete().then(() => { console.log("Séance supprimée:", currentWorkoutId); workouts = workouts.filter(w => w.id !== currentWorkoutId); currentWorkoutId = null; showPage('home-page'); renderWorkoutsList(); }).catch(handleFirestoreError).finally(() => { deleteWorkoutBtn.disabled = false; deleteWorkoutBtn.textContent = 'Supprimer cette séance'; }); }
169
- function setTodayDate() { /* ... */ if(workoutDateInput) try { workoutDateInput.value = new Date().toISOString().split('T')[0]; } catch(e){ console.error("Erreur date:", e); }}
170
- 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') { loadAndRenderWorkoutTypes(); } if (pageId === 'select-workout-type-page') { populateWorkoutTypeSelector(); } } 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'); }
171
- 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); }
172
  function handleAddNewExercise() { addExercise(true); }
173
- 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(); }
174
- 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()); }
175
- function navigateExerciseForm(direction) { /* ... */ workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); const newIndex = currentExerciseIndex + direction; if (newIndex >= 0 && newIndex < workoutExercisesForm.length) { currentExerciseIndex = newIndex; renderActiveExerciseForm(); } }
176
- 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(); }
177
- 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; } }
178
- function clearNewWorkoutForm(typeName = "Libre") { /* ... (Ajout param typeName) ... */ console.log("Nettoyage form pour type:", typeName); if (!currentFirebaseUser) return; selectedWorkoutTypeName = typeName; // Stocker le type choisi const workoutNameInput = document.getElementById('workout-name'); if(workoutNameInput) workoutNameInput.value = (typeName === "Libre" ? "" : typeName); // Pré-remplir nom si pas libre 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(); }
179
  function updateWorkoutTypeIndicator() { if (!workoutTypeIndicator) return; if (selectedWorkoutTypeName && selectedWorkoutTypeName !== "Libre") { workoutTypeIndicator.textContent = selectedWorkoutTypeName; workoutTypeIndicator.classList.remove('hidden'); } else { workoutTypeIndicator.classList.add('hidden'); } }
180
- function renderWorkoutsList() { /* ... (Ajout affichage type) ... */ 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); }); } updateStats(); }
181
- function displayWorkoutDetails(workoutId) { /* ... (Ajout affichage type) ... */ if (!currentFirebaseUser) return; const workout = workouts.find(w => w.id === workoutId); if (!workout) { showPage('home-page'); return; } // Afficher le type dans le titre 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) => { /* ... affichage exercice identique ... */ 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'); }
182
- function updateStats() { /* ... (Pas de changement ici pour l'instant) ... */ if (!currentFirebaseUser) return; const workoutCount = workouts.length; document.getElementById('stats-workout-count').textContent = workoutCount; if (workoutCount === 0) { document.getElementById('stats-avg-tonnage').textContent = '0'; document.getElementById('stats-avg-satisfaction').textContent = '0%'; return; } const totalTonnageAll = workouts.reduce((sum, workout) => sum + (workout.totalTonnage || 0), 0); const avgTonnage = totalTonnageAll / workoutCount; document.getElementById('stats-avg-tonnage').textContent = avgTonnage.toFixed(1); const totalSatisfaction = workouts.reduce((sum, workout) => sum + (workout.satisfaction || 0), 0); const avgSatisfaction = totalSatisfaction / workoutCount; document.getElementById('stats-avg-satisfaction').textContent = `${Math.round(avgSatisfaction)}%`; }
183
  function handleFirestoreError(error) { console.error("Erreur Firestore:", error); alert("Une erreur est survenue avec la base de données."); }
184
 
185
  // --- ÉCOUTEURS D'ÉVÉNEMENTS (Attachés ici) ---
186
  function initEventListeners() {
187
- console.log("Attachement écouteurs...");
188
- if (loginForm) loginForm.addEventListener('submit', handleAuthAction); else console.error("loginForm manquant");
189
- if (authSwitchLink) authSwitchLink.addEventListener('click', authSwitchMode); else console.error("authSwitchLink manquant");
190
- if (logoutBtn) logoutBtn.addEventListener('click', handleLogout); else console.error("logoutBtn manquant");
191
-
192
- navItems.forEach((item, index) => { if(item) item.addEventListener('click', (e) => { e.preventDefault(); if (!currentFirebaseUser) return; const targetPageId = item.getAttribute('data-page'); showPage(targetPageId); navItems.forEach(nav => nav.classList.remove('active')); item.classList.add('active'); if (targetPageId === 'stats-page') { updateStats(); } }); else console.error(`navItem #${index} manquant`); });
193
-
194
- if (newWorkoutBtn) newWorkoutBtn.addEventListener('click', () => { console.log("Clic 'Nouvelle séance'"); if (!currentFirebaseUser) { alert("Reconnectez-vous."); return; } showPage('select-workout-type-page'); /* Afficher la page de sélection */ }); else console.error("newWorkoutBtn manquant");
195
-
196
- if (manageTypesBtn) manageTypesBtn.addEventListener('click', () => showPage('manage-types-page')); else console.error("manageTypesBtn manquant");
197
- if (addTypeForm) addTypeForm.addEventListener('submit', handleAddWorkoutType); else console.error("addTypeForm manquant");
198
- if (selectWorkoutTypeDropdown) selectWorkoutTypeDropdown.addEventListener('change', updateStartStructuredBtnState); else console.error("selectWorkoutTypeDropdown manquant");
199
-
200
- if (startStructuredWorkoutBtn) startStructuredWorkoutBtn.addEventListener('click', () => { if(selectWorkoutTypeDropdown) { const type = selectWorkoutTypeDropdown.value; if(type) { clearNewWorkoutForm(type); showPage('new-workout-page'); } } }); else console.error("startStructuredWorkoutBtn manquant");
201
- if (startFreeWorkoutBtn) startFreeWorkoutBtn.addEventListener('click', () => { clearNewWorkoutForm("Libre"); showPage('new-workout-page'); }); else console.error("startFreeWorkoutBtn manquant");
202
-
203
- backToHomeBtns.forEach(btn => { if(btn) btn.addEventListener('click', () => showPage('home-page')); else console.error("Un backToHomeBtn manquant"); }); // Boutons retour génériques
204
-
205
- if (cancelNewWorkoutBtn) cancelNewWorkoutBtn.addEventListener('click', () => showPage('home-page')); else console.error("cancelNewWorkoutBtn manquant"); // Annuler depuis form séance
206
- if (saveWorkoutBtn) saveWorkoutBtn.addEventListener('click', saveWorkout); else console.error("saveWorkoutBtn manquant");
207
- if (addExerciseBtn) addExerciseBtn.addEventListener('click', handleAddNewExercise); else console.error("addExerciseBtn manquant");
208
- if (prevExerciseBtn) prevExerciseBtn.addEventListener('click', navigateExerciseForm.bind(null, -1)); else console.error("prevExerciseBtn manquant");
209
- if (nextExerciseBtn) nextExerciseBtn.addEventListener('click', navigateExerciseForm.bind(null, 1)); else console.error("nextExerciseBtn manquant");
210
- if (deleteWorkoutBtn) deleteWorkoutBtn.addEventListener('click', deleteWorkout); else console.error("deleteWorkoutBtn manquant");
211
- if (satisfactionRange) satisfactionRange.addEventListener('input', () => { if(satisfactionValue) satisfactionValue.textContent = `${satisfactionRange.value}%`; }); else console.error("satisfactionRange manquant");
212
- console.log("Écouteurs attachés.");
213
  }
214
 
215
  // ==================================================
@@ -217,9 +111,16 @@
217
  // ==================================================
218
 
219
  // --- INITIALISATION ---
220
- initAuthListener(); // Démarrer l'écoute AUTH
221
- initEventListeners(); // Attacher les actions aux boutons etc.
222
-
223
- </script>
 
 
 
 
 
 
 
224
  </body>
225
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>LiftTrack - Firebase Edition v4</title>
7
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>
8
 
9
  <!-- SDK Firebase (v8) -->
 
12
  <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
13
 
14
  <style>
15
+ /* CSS Identique */
16
+ :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 { 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; } }
 
 
 
 
 
 
 
 
 
 
17
  </style>
18
  </head>
19
  <body>
20
+ <!-- HTML Body Identique -->
21
+ <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 de Séances</h2> <button class="btn btn-outline back-to-home-btn">Retour Accueil</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 Nouveau Type</h3> <form id="add-type-form"> <input type="text" id="new-type-name" placeholder="Nom du type (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 une Nouvelle Séance</h2> <button class="btn btn-outline back-to-home-btn">Annuler</button> </div> <div class="card"> <h3>Choisir un Type Prédéfini</h3> <select id="select-workout-type"> <option value="">-- Sélectionner un type --</option> </select> <button id="start-structured-workout-btn" class="btn" disabled>Démarrer Séance Structurée</button> </div> <div class="card"> <h3>Ou démarrer une séance libre</h3> <button id="start-free-workout-btn" class="btn btn-info">Démarrer Séance 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 Séance</button> </div> </div> <div class="card"> <div class="form-group"> <label for="workout-name">Nom de la séance (optionnel si type défini)</label> <input type="text" id="workout-name" placeholder="Ex: Push Intense, Séance du Lundi..."> </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 un Nouvel Exercice</button> <div class="card" style="margin-top: 2rem;"> <div class="form-group"> <label for="satisfaction">Niveau de satisfaction (1-100%)</label> <input type="range" id="satisfaction" min="1" max="100" value="75"> <div class="satisfaction"> <span>Satisfaction</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 Total (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 cette séance</button> </div> <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 Moyenne</div> </div> </div> <h3 style="margin: 1.5rem 0 1rem;">Tendances et Comparaisons par Type</h3> <div class="card"> <p style="text-align: center; margin: 1rem 0; color: #888;"> (Fonctionnalité de comparaison par type 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>
22
 
23
+ <script> // Début du script principal
24
+ console.log("Script démarré.");
25
 
26
+ // =============== AJOUT FIREBASE ===============
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  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" };
28
+ let app, auth, db; // Déclarer ici
29
+ try {
30
+ app = firebase.initializeApp(firebaseConfig);
31
+ auth = firebase.auth();
32
+ db = firebase.firestore();
33
+ console.log("Firebase Initialisé !");
34
+ } catch (e) {
35
+ console.error("Erreur Initialisation Firebase:", e);
36
+ alert("Impossible d'initialiser la connexion à la base de données. Vérifiez la console.");
37
+ // Bloquer l'app si Firebase échoue ? Ou afficher un message permanent ?
38
+ }
39
+ // =============== FIN AJOUT FIREBASE ===============
40
 
41
  // --- Variables d'État ---
42
+ let workouts = []; let currentWorkoutId = null; let currentExerciseIndex = 0; let workoutExercisesForm = []; let currentFirebaseUser = null; let userWorkoutTypes = []; let selectedWorkoutTypeName = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ // --- Éléments DOM (Déclarer d'abord les constantes) ---
45
+ 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');
46
  let isLoginMode = true;
47
 
48
  // ===========================================
49
+ // --- DÉFINITION DE TOUTES LES FONCTIONS ---
50
  // ===========================================
51
 
52
+ 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(); } }); }
53
+ function showLoginPage() { if(loginPage) loginPage.style.display = 'block'; if(mainAppContent) mainAppContent.style.display = 'none'; }
54
+ 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'); }
55
+ 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'; }); } }
56
+ function handleLogout() { if(!auth) return; console.log("Déconnexion..."); auth.signOut().catch(handleAuthError); }
57
+ 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'; }
 
58
  function handleAuthError(error) { console.error("Erreur Auth:", error); loginError.textContent = getAuthErrorMessage(error); }
59
  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.'; } }
60
+ 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'); }); }
61
+ 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); }); } }
62
+ 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); }
63
+ function populateWorkoutTypeSelector() { if (!selectWorkoutTypeDropdown) return; selectWorkoutTypeDropdown.innerHTML = '<option value="">-- Sélectionner un type --</option>'; userWorkoutTypes.forEach(type => { const option = document.createElement('option'); option.value = type.name; option.textContent = type.name; selectWorkoutTypeDropdown.appendChild(option); }); updateStartStructuredBtnState(); }
 
 
64
  function updateStartStructuredBtnState() { if (!selectWorkoutTypeDropdown || !startStructuredWorkoutBtn) return; startStructuredWorkoutBtn.disabled = !selectWorkoutTypeDropdown.value; }
65
+ 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(); showPage('home-page'); }).catch(handleFirestoreError).finally(() => { if (spinner) spinner.classList.add('hidden'); }); }
66
+ 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'; }); }
67
+ 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'; }); }
68
+ function setTodayDate() { if(workoutDateInput) try { workoutDateInput.value = new Date().toISOString().split('T')[0]; } catch(e){ console.error("Erreur date:", e); }}
69
+ 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(); } } 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'); }
70
+ 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); }
 
 
 
71
  function handleAddNewExercise() { addExercise(true); }
72
+ 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(); }
73
+ 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()); }
74
+ function navigateExerciseForm(direction) { workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); const newIndex = currentExerciseIndex + direction; if (newIndex >= 0 && newIndex < workoutExercisesForm.length) { currentExerciseIndex = newIndex; renderActiveExerciseForm(); } }
75
+ 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(); }
76
+ 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; } }
77
+ 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(); }
78
  function updateWorkoutTypeIndicator() { if (!workoutTypeIndicator) return; if (selectedWorkoutTypeName && selectedWorkoutTypeName !== "Libre") { workoutTypeIndicator.textContent = selectedWorkoutTypeName; workoutTypeIndicator.classList.remove('hidden'); } else { workoutTypeIndicator.classList.add('hidden'); } }
79
+ 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); }); } updateStats(); }
80
+ 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'); }
81
+ function updateStats() { if (!currentFirebaseUser) return; const workoutCount = workouts.length; const statsWorkoutCountEl = document.getElementById('stats-workout-count'); const statsAvgTonnageEl = document.getElementById('stats-avg-tonnage'); const statsAvgSatisfactionEl = document.getElementById('stats-avg-satisfaction'); 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)}%`; }
82
  function handleFirestoreError(error) { console.error("Erreur Firestore:", error); alert("Une erreur est survenue avec la base de données."); }
83
 
84
  // --- ÉCOUTEURS D'ÉVÉNEMENTS (Attachés ici) ---
85
  function initEventListeners() {
86
+ console.log("initEventListeners: Attachement...");
87
+ // Check elements before attaching
88
+ if (loginForm) loginForm.addEventListener('submit', handleAuthAction); else console.error("!loginForm");
89
+ if (authSwitchLink) authSwitchLink.addEventListener('click', authSwitchMode); else console.error("!authSwitchLink");
90
+ if (logoutBtn) logoutBtn.addEventListener('click', handleLogout); else console.error("!logoutBtn");
91
+ navItems.forEach((item, index) => { if(item) item.addEventListener('click', (e) => { e.preventDefault(); if (!currentFirebaseUser) return; const targetPageId = item.getAttribute('data-page'); showPage(targetPageId); /* ... */ }); else console.error(`!navItem #${index}`); });
92
+ if (newWorkoutBtn) newWorkoutBtn.addEventListener('click', () => { if (!currentFirebaseUser) return; showPage('select-workout-type-page'); }); else console.error("!newWorkoutBtn");
93
+ if (manageTypesBtn) manageTypesBtn.addEventListener('click', () => showPage('manage-types-page')); else console.error("!manageTypesBtn");
94
+ if (addTypeForm) addTypeForm.addEventListener('submit', handleAddWorkoutType); else console.error("!addTypeForm");
95
+ if (selectWorkoutTypeDropdown) selectWorkoutTypeDropdown.addEventListener('change', updateStartStructuredBtnState); else console.error("!selectWorkoutTypeDropdown");
96
+ if (startStructuredWorkoutBtn) startStructuredWorkoutBtn.addEventListener('click', () => { if(selectWorkoutTypeDropdown) { const type = selectWorkoutTypeDropdown.value; if(type) { clearNewWorkoutForm(type); showPage('new-workout-page'); } } }); else console.error("!startStructuredWorkoutBtn");
97
+ if (startFreeWorkoutBtn) startFreeWorkoutBtn.addEventListener('click', () => { clearNewWorkoutForm("Libre"); showPage('new-workout-page'); }); else console.error("!startFreeWorkoutBtn");
98
+ backToHomeBtns.forEach(btn => { if(btn) btn.addEventListener('click', () => showPage('home-page')); else console.error("!backToHomeBtn"); });
99
+ if (cancelNewWorkoutBtn) cancelNewWorkoutBtn.addEventListener('click', () => showPage('home-page')); else console.error("!cancelNewWorkoutBtn");
100
+ if (saveWorkoutBtn) saveWorkoutBtn.addEventListener('click', saveWorkout); else console.error("!saveWorkoutBtn");
101
+ if (addExerciseBtn) addExerciseBtn.addEventListener('click', handleAddNewExercise); else console.error("!addExerciseBtn");
102
+ if (prevExerciseBtn) prevExerciseBtn.addEventListener('click', navigateExerciseForm.bind(null, -1)); else console.error("!prevExerciseBtn");
103
+ if (nextExerciseBtn) nextExerciseBtn.addEventListener('click', navigateExerciseForm.bind(null, 1)); else console.error("!nextExerciseBtn");
104
+ if (deleteWorkoutBtn) deleteWorkoutBtn.addEventListener('click', deleteWorkout); else console.error("!deleteWorkoutBtn");
105
+ if (satisfactionRange) satisfactionRange.addEventListener('input', () => { if(satisfactionValue) satisfactionValue.textContent = `${satisfactionRange.value}%`; }); else console.error("!satisfactionRange");
106
+ console.log("initEventListeners: Fin attachement.");
 
 
 
 
 
107
  }
108
 
109
  // ==================================================
 
111
  // ==================================================
112
 
113
  // --- INITIALISATION ---
114
+ // S'assurer que Firebase est prêt avant d'écouter l'état d'auth
115
+ if (auth) {
116
+ initAuthListener();
117
+ } else {
118
+ console.error("Firebase Auth non initialisé, impossible d'écouter l'état d'authentification.");
119
+ // Afficher un message d'erreur à l'utilisateur ici ?
120
+ }
121
+ // Attacher les écouteurs d'événements aux éléments du DOM
122
+ initEventListeners();
123
+
124
+ </script> // Fin de la balise script principale
125
  </body>
126
  </html>