Spaces:
Running
Running
Update index.html
Browse files- index.html +166 -224
index.html
CHANGED
@@ -3,55 +3,35 @@
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
-
<!-- Titre mis à jour -->
|
7 |
<title>Cocktail Fitness - Suivi</title>
|
8 |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>
|
9 |
|
10 |
-
<!-- SDK Firebase (v8)
|
11 |
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
|
12 |
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
|
13 |
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
|
14 |
|
15 |
-
<!--
|
16 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
17 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
18 |
<link href="https://fonts.googleapis.com/css2?family=Oswald:wght@700&family=Roboto:wght@400;700&display=swap" rel="stylesheet">
|
19 |
|
20 |
|
21 |
<style>
|
22 |
-
/*
|
23 |
:root {
|
24 |
-
--bg-dark: #101010;
|
25 |
-
--
|
26 |
-
--text-light: #f0f0f0; /* Texte principal légèrement off-white */
|
27 |
-
--text-secondary: #aaaaaa; /* Texte secondaire (labels, etc.) */
|
28 |
-
--accent: #FFC107; /* Jaune/Or du logo Cocktail */
|
29 |
-
--accent-dark: #E0A800; /* Jaune plus sombre pour hover */
|
30 |
-
--text-on-accent: #101010; /* Texte sur fond jaune (assez foncé pour contraste) */
|
31 |
-
--neutral-white: #FFFFFF; /* Blanc pur du logo Fitness */
|
32 |
-
--danger: #f44336; /* Rouge pour danger (inchangé) */
|
33 |
-
--info: #6c757d; /* Couleur neutre/grise pour info (ancien bleu) */
|
34 |
-
--success: #4CAF50; /* Vert pour succès */
|
35 |
-
--border-color: #333333; /* Couleur des bordures */
|
36 |
-
|
37 |
-
--font-primary: 'Roboto', sans-serif; /* Police par défaut */
|
38 |
-
--font-headings: 'Oswald', sans-serif; /* Police pour titres/boutons (style COCKTAIL) */
|
39 |
}
|
40 |
-
/* ===== FIN NOUVELLES VARIABLES ===== */
|
41 |
-
|
42 |
* { margin: 0; padding: 0; box-sizing: border-box; font-family: var(--font-primary); }
|
43 |
body { background-color: var(--bg-dark); color: var(--text-light); min-height: 100vh; padding-bottom: 80px; }
|
44 |
.container { width: 100%; max-width: 800px; margin: 0 auto; padding: 1rem; }
|
45 |
-
|
46 |
-
/* Header */
|
47 |
header { padding: 1rem 0; text-align: center; border-bottom: 1px solid var(--border-color); margin-bottom: 1rem; display: flex; flex-direction: column; align-items: center; }
|
48 |
-
|
|
|
|
|
49 |
header p { color: var(--text-secondary); font-size: 0.9rem; }
|
50 |
-
|
51 |
-
/* Typography */
|
52 |
h1, h2, h3, h4 { font-family: var(--font-headings); font-weight: 700; color: var(--accent); text-transform: uppercase; letter-spacing: 0.5px; }
|
53 |
-
|
54 |
-
/* Buttons */
|
55 |
.btn { background-color: var(--accent); color: var(--text-on-accent); border: none; padding: 0.6rem 1.2rem; border-radius: 4px; cursor: pointer; font-weight: bold; font-family: var(--font-headings); text-transform: uppercase; transition: background-color 0.2s, color 0.2s; }
|
56 |
.btn:hover { background-color: var(--accent-dark); color: var(--text-on-accent); }
|
57 |
.btn:disabled { background-color: #555; color: #999; cursor: not-allowed; }
|
@@ -62,8 +42,6 @@
|
|
62 |
.btn-danger:hover { background-color: #d32f2f; color: white; }
|
63 |
.btn-info { background-color: var(--info); color: var(--text-light); border-color: var(--info); }
|
64 |
.btn-info:hover { background-color: #5a6268; color: var(--text-light); }
|
65 |
-
|
66 |
-
/* Forms */
|
67 |
input, select, textarea { width: 100%; padding: 0.7rem; margin-bottom: 1rem; background-color: #2a2a2a; border: 1px solid #444; border-radius: 4px; color: var(--text-light); font-size: 1rem; }
|
68 |
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.3); }
|
69 |
input[type="checkbox"] { width: auto; margin-right: 0.5rem; vertical-align: middle; accent-color: var(--accent); }
|
@@ -71,25 +49,17 @@
|
|
71 |
.form-group { margin-bottom: 1rem; }
|
72 |
.form-row { display: flex; gap: 1rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
|
73 |
.form-row > * { flex: 1; margin-bottom: 0; min-width: 100px; }
|
74 |
-
|
75 |
-
/* Cards */
|
76 |
.card { background-color: var(--bg-card); border-radius: 8px; padding: 1.2rem; margin-bottom: 1rem; box-shadow: 0 4px 8px rgba(0,0,0,0.3); }
|
77 |
-
|
78 |
-
/* Exercise Styling */
|
79 |
.exercise { border-left: 4px solid var(--accent); padding-left: 1rem; margin-bottom: 1.5rem; display: none; background-color: rgba(0,0,0,0.1); padding: 1rem; border-radius: 0 4px 4px 0; }
|
80 |
.exercise.active-exercise { display: block; animation: fadeIn 0.3s ease-in-out; }
|
81 |
.exercise-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; gap: 0.5rem;}
|
82 |
.exercise-header input { margin-bottom: 0; }
|
83 |
.series-container { margin-left: 0rem; margin-top: 1rem; }
|
84 |
.series { background-color: #282828; padding: 0.8rem; border-radius: 4px; margin-bottom: 0.8rem; border: 1px solid #333; }
|
85 |
-
|
86 |
-
/* Nav Bottom */
|
87 |
.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 -3px 10px rgba(0,0,0,0.4); z-index: 10; }
|
88 |
.nav-item { text-align: center; color: var(--text-secondary); text-decoration: none; font-size: 0.85rem; transition: color 0.2s; padding: 0 0.5rem; }
|
89 |
.nav-item.active { color: var(--accent); font-weight: bold; }
|
90 |
.nav-icon { font-size: 1.5rem; margin-bottom: 0.2rem; }
|
91 |
-
|
92 |
-
/* Workout Card */
|
93 |
.workout-card { border-left: 4px solid var(--accent); cursor: pointer; transition: transform 0.2s ease-out, box-shadow 0.2s ease-out; }
|
94 |
.workout-card:hover { transform: translateY(-3px); box-shadow: 0 6px 12px rgba(0,0,0,0.4); }
|
95 |
.workout-header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem; }
|
@@ -98,72 +68,45 @@
|
|
98 |
.workout-details { font-size: 0.9rem; color: var(--text-secondary); margin-top: 0.8rem; }
|
99 |
.workout-details span { margin-right: 0.8rem; }
|
100 |
.workout-header h3 .badge.type-badge { background-color: var(--neutral-white); color: var(--bg-dark); padding: 0.3rem 0.6rem; border-radius: 12px; font-size: 0.8rem; font-weight: bold; white-space: nowrap; margin-left: 5px; position: static; flex-shrink: initial; margin-left: 5px; }
|
101 |
-
|
102 |
-
/* Stats */
|
103 |
.stat-card { text-align: center; padding: 1rem; }
|
104 |
.stat-value { font-size: 2rem; color: var(--accent); font-weight: bold; font-family: var(--font-headings); margin-bottom: 0.3rem; }
|
105 |
.stat-card div:last-child { color: var(--text-secondary); font-size: 0.9rem; }
|
106 |
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1rem; }
|
107 |
-
|
108 |
-
/* Utility */
|
109 |
.hidden { display: none !important; }
|
110 |
#app-container > div:not(.active) { display: none !important; }
|
111 |
.flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; }
|
112 |
-
|
113 |
-
/* Badges (General) */
|
114 |
.badge { background-color: var(--accent); color: var(--text-on-accent); padding: 0.3rem 0.6rem; border-radius: 12px; font-size: 0.8rem; font-weight: bold; white-space: nowrap; margin-left: 5px; }
|
115 |
-
/* .badge.type-badge is handled in .workout-header h3 .badge.type-badge */
|
116 |
-
|
117 |
-
/* Spinner */
|
118 |
.spinner { border: 4px solid rgba(255, 255, 255, 0.1); width: 36px; height: 36px; border-radius: 50%; border-left-color: var(--accent); animation: spin 1s linear infinite; margin: 2rem auto; display: none; }
|
119 |
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
120 |
-
|
121 |
-
/* Exercise Summary in Details */
|
122 |
.exercise-summary { margin: 0.3rem 0; padding: 0.5rem 0; border-bottom: 1px solid var(--border-color); font-size: 0.9rem; }
|
123 |
.exercise-summary:last-child { border-bottom: none; }
|
124 |
.series-summary-container .badge { font-size: 0.7rem; padding: 0.2rem 0.4rem; background-color: var(--info); color: var(--text-light); }
|
125 |
-
|
126 |
-
/* Satisfaction Slider */
|
127 |
.satisfaction { display: flex; align-items: center; justify-content: center; flex-direction: column; margin-top: 1rem; }
|
128 |
input[type="range"] { accent-color: var(--accent); cursor: pointer;}
|
129 |
.satisfaction-value { font-size: 2.2rem; color: var(--accent); margin-top: 0.5rem; font-weight: bold; font-family: var(--font-headings); }
|
130 |
-
|
131 |
-
/* Exercise Navigation */
|
132 |
.exercise-navigation { display: flex; justify-content: space-between; margin-top: 1.5rem; margin-bottom: 1.5rem; align-items: center;}
|
133 |
.exercise-navigation span { color: var(--text-secondary); font-style: italic; }
|
134 |
-
|
135 |
-
/* Login Page */
|
136 |
#login-page .card { max-width: 400px; margin: 3rem auto; }
|
137 |
#login-page h2 { text-align: center; margin-bottom: 1.5rem; color: var(--accent); }
|
138 |
-
/* Error/Success message styling */
|
139 |
#login-message { text-align: center; margin-top: 1rem; font-size: 0.9rem; min-height: 1.2em; font-weight: bold; display: none; }
|
140 |
#login-message.error { color: var(--danger); }
|
141 |
#login-message.success { color: var(--success); }
|
142 |
#login-form button { margin-top: 1rem; }
|
143 |
-
.auth-options { margin-top: 1.5rem; font-size: 0.9rem; text-align: center; }
|
144 |
-
.auth-switch { margin-bottom: 0.5rem; color: var(--text-secondary); }
|
145 |
.auth-options a { color: var(--accent); cursor: pointer; text-decoration: underline; font-weight: bold; }
|
146 |
-
#forgot-password-link { display: block;
|
147 |
-
|
148 |
-
|
149 |
-
/* User Info */
|
150 |
.user-info { text-align: right; margin-bottom: 1rem; font-size: 0.9rem; color: var(--text-secondary);}
|
151 |
.user-info strong { color: var(--text-light); margin: 0 5px; }
|
152 |
.user-info button { margin-left: 0.8rem; padding: 0.3rem 0.7rem; font-size: 0.8rem; }
|
153 |
-
|
154 |
-
/* Manage Types Page */
|
155 |
#workout-types-list li { display: flex; justify-content: space-between; align-items: center; background-color: #2a2a2a; padding: 0.7rem 1rem; margin-bottom: 0.5rem; border-radius: 4px; border-left: 4px solid var(--neutral-white); color: var(--text-light); font-weight: bold; }
|
156 |
#add-type-form { display: flex; gap: 0.5rem; align-items: flex-end;}
|
157 |
#add-type-form input { margin-bottom: 0; flex-grow: 1;}
|
158 |
#add-type-form button { flex-shrink: 0; }
|
159 |
#add-type-error { color: var(--danger); font-size: 0.9rem; margin-top: 0.5rem; font-weight: bold; display: none; }
|
160 |
-
|
161 |
-
/* Select Workout Type Page */
|
162 |
#select-workout-type-page .card { text-align: center; }
|
163 |
#select-workout-type-page .btn { margin-top: 1rem; width: 80%; }
|
164 |
#select-workout-type { margin-bottom: 1rem;}
|
165 |
-
|
166 |
-
/* Type Trend Card */
|
167 |
.type-trend-card { margin-bottom: 1.5rem; }
|
168 |
.type-trend-card h4 { color: var(--neutral-white); border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; margin-bottom: 1rem; text-transform: uppercase; }
|
169 |
.type-trend-card ul { list-style: none; padding: 0; font-size: 0.95rem; }
|
@@ -171,7 +114,10 @@
|
|
171 |
.type-trend-card li:last-child { border-bottom: none; }
|
172 |
.type-trend-card li span:first-child { color: var(--text-secondary); font-size: 0.85rem; margin-right: 1rem; }
|
173 |
.type-trend-card li strong { color: var(--accent); }
|
174 |
-
|
|
|
|
|
|
|
175 |
|
176 |
/* Media Queries */
|
177 |
@media (max-width: 600px) {
|
@@ -189,16 +135,16 @@
|
|
189 |
#add-type-form { flex-direction: column; align-items: stretch;}
|
190 |
header img#app-logo { max-width: 200px; }
|
191 |
}
|
192 |
-
|
193 |
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
194 |
-
|
195 |
</style>
|
196 |
</head>
|
197 |
<body>
|
198 |
<div class="container">
|
199 |
-
<!-- Header -->
|
200 |
<header>
|
201 |
-
<
|
|
|
|
|
202 |
<p>Votre suivi de séances personnalisé</p>
|
203 |
</header>
|
204 |
|
@@ -209,17 +155,14 @@
|
|
209 |
<form id="login-form" novalidate>
|
210 |
<div class="form-group"> <label for="auth-email">Email</label> <input type="email" id="auth-email" placeholder="[email protected]" required> </div>
|
211 |
<div class="form-group"> <label for="auth-password">Mot de passe</label> <input type="password" id="auth-password" placeholder="********" required> </div>
|
212 |
-
<!-- Message pour erreurs OU succès (ex: email reset envoyé) -->
|
213 |
<p id="login-message" style="display: none;"></p>
|
214 |
<button type="submit" id="auth-action-btn" class="btn" style="width: 100%;">Se Connecter</button>
|
215 |
</form>
|
216 |
-
<!-- Options supplémentaires sous le formulaire -->
|
217 |
<div class="auth-options">
|
218 |
<div class="auth-switch">
|
219 |
<span id="auth-switch-text">Pas encore de compte ?</span>
|
220 |
<a id="auth-switch-link">Inscrivez-vous ici</a>
|
221 |
</div>
|
222 |
-
<!-- Lien Mot de passe oublié -->
|
223 |
<a href="#" id="forgot-password-link">Mot de passe oublié ?</a>
|
224 |
</div>
|
225 |
</div>
|
@@ -233,9 +176,30 @@
|
|
233 |
</div>
|
234 |
|
235 |
<div id="app-container">
|
236 |
-
<!--
|
237 |
-
|
238 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
239 |
<div class="flex-between">
|
240 |
<h2>Mes séances</h2>
|
241 |
<div>
|
@@ -253,7 +217,8 @@
|
|
253 |
<div id="manage-types-page">
|
254 |
<div class="flex-between">
|
255 |
<h2>Gérer les Types</h2>
|
256 |
-
|
|
|
257 |
</div>
|
258 |
<div class="card">
|
259 |
<h3>Types Existants</h3>
|
@@ -276,7 +241,8 @@
|
|
276 |
<div id="select-workout-type-page">
|
277 |
<div class="flex-between">
|
278 |
<h2>Démarrer Séance</h2>
|
279 |
-
|
|
|
280 |
</div>
|
281 |
<div class="card">
|
282 |
<h3>Séance Structurée</h3>
|
@@ -328,7 +294,8 @@
|
|
328 |
<div id="workout-details-page">
|
329 |
<div class="flex-between">
|
330 |
<h2>Détail Séance <span id="detail-workout-type" class="badge type-badge hidden"></span></h2>
|
331 |
-
|
|
|
332 |
</div>
|
333 |
<div class="card">
|
334 |
<div class="workout-details-info">
|
@@ -367,9 +334,13 @@
|
|
367 |
|
368 |
</div> <!-- Fin #app-container -->
|
369 |
|
370 |
-
<!-- Bottom Navigation -->
|
371 |
<nav class="nav-bottom">
|
372 |
-
|
|
|
|
|
|
|
|
|
373 |
<a href="#" class="nav-item" data-page="stats-page"> <div class="nav-icon">📊</div> <div>Stats</div> </a>
|
374 |
</nav>
|
375 |
|
@@ -383,16 +354,8 @@
|
|
383 |
// Firebase Init
|
384 |
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" };
|
385 |
let app, auth, db;
|
386 |
-
try {
|
387 |
-
|
388 |
-
auth = firebase.auth();
|
389 |
-
db = firebase.firestore();
|
390 |
-
console.log("Firebase Initialisé !");
|
391 |
-
} catch (e) {
|
392 |
-
console.error("Erreur Init Firebase:", e);
|
393 |
-
alert("Erreur critique: Impossible d'initialiser Firebase.");
|
394 |
-
document.body.innerHTML = '<div style="color: red; padding: 20px;">Erreur critique: Impossible d\'initialiser Firebase. Vérifiez la configuration et la connexion.</div>';
|
395 |
-
}
|
396 |
|
397 |
// --- Variables d'État ---
|
398 |
let workouts = []; let currentWorkoutId = null; let currentExerciseIndex = 0; let workoutExercisesForm = []; let currentFirebaseUser = null; let userWorkoutTypes = []; let selectedWorkoutTypeName = null; let isLoginMode = true;
|
@@ -405,10 +368,11 @@
|
|
405 |
const loginForm = document.getElementById('login-form');
|
406 |
const authActionButton = document.getElementById('auth-action-btn');
|
407 |
const authSwitchLink = document.getElementById('auth-switch-link');
|
408 |
-
const forgotPasswordLink = document.getElementById('forgot-password-link');
|
|
|
409 |
const authTitle = document.getElementById('auth-title');
|
410 |
const authSwitchText = document.getElementById('auth-switch-text');
|
411 |
-
const loginMessage = document.getElementById('login-message');
|
412 |
const currentUserDisplay = document.getElementById('current-user-display');
|
413 |
const logoutBtn = document.getElementById('logout-btn');
|
414 |
const spinner = document.querySelector('#workouts-list .spinner');
|
@@ -420,7 +384,7 @@
|
|
420 |
const addExerciseBtn = document.getElementById('add-exercise-btn');
|
421 |
const exercisesContainer = document.getElementById('exercises-container');
|
422 |
const workoutsList = document.getElementById('workouts-list');
|
423 |
-
const
|
424 |
const deleteWorkoutBtn = document.getElementById('delete-workout-btn');
|
425 |
const satisfactionRange = document.getElementById('satisfaction');
|
426 |
const satisfactionValue = document.querySelector('.satisfaction-value');
|
@@ -449,125 +413,119 @@
|
|
449 |
const typeTrendsContainer = document.getElementById('type-trends-container');
|
450 |
const noTrendsMessage = document.getElementById('no-trends-message');
|
451 |
const trendsSpinner = document.getElementById('trends-spinner');
|
|
|
|
|
|
|
452 |
|
453 |
// ===========================================
|
454 |
// --- DÉFINITION DES FONCTIONS ---
|
455 |
// ===========================================
|
456 |
|
457 |
-
function showMessage(message, isError = true) {
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
function initAuthListener() { /* ... identique ... */ if (!auth) { console.error("Auth object not ready in initAuthListener"); return; } auth.onAuthStateChanged(user => { console.log("Auth state changed:", user ? user.uid : 'null'); currentFirebaseUser = user; if (user) { showApp(); } else { workouts = []; userWorkoutTypes = []; showLoginPage(); } }); }
|
474 |
-
function showLoginPage() { /* ... identique ... */ if(loginPage) loginPage.style.display = 'block'; if(mainAppContent) mainAppContent.style.display = 'none'; isLoginMode = true; authSwitchMode(); }
|
475 |
-
function showApp() { /* ... identique ... */ if (!currentFirebaseUser || !mainAppContent) { console.error("Cannot show app: No user or main content missing."); showLoginPage(); return; } console.log("Affichage App for user:", currentFirebaseUser.email); if(loginPage) loginPage.style.display = 'none'; mainAppContent.style.display = 'block'; if(currentUserDisplay) currentUserDisplay.textContent = currentFirebaseUser.email; setTodayDate(); loadWorkouts(); loadWorkoutTypes(); showPage('home-page'); }
|
476 |
-
|
477 |
-
function handleAuthAction(event) {
|
478 |
-
if (!auth || !authEmailInput || !authPasswordInput || !authActionButton) return;
|
479 |
-
event.preventDefault();
|
480 |
-
const email = authEmailInput.value;
|
481 |
-
const password = authPasswordInput.value;
|
482 |
-
clearMessage(); // **MODIFIÉ**
|
483 |
-
if (!email || !password) { showMessage('Email et Mot de passe requis.'); return; } // **MODIFIÉ**
|
484 |
-
authActionButton.disabled = true; authActionButton.textContent = 'Chargement...';
|
485 |
-
if (isLoginMode) { auth.signInWithEmailAndPassword(email, password).catch(handleAuthError).finally(() => { authActionButton.disabled = false; authActionButton.textContent = 'Se Connecter'; }); }
|
486 |
-
else { auth.createUserWithEmailAndPassword(email, password).catch(handleAuthError).finally(() => { authActionButton.disabled = false; authActionButton.textContent = 'S\'inscrire'; }); }
|
487 |
}
|
488 |
|
489 |
-
|
490 |
-
function handlePasswordReset(event) {
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
496 |
|
497 |
-
|
498 |
-
|
499 |
-
|
|
|
500 |
}
|
501 |
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
}
|
514 |
-
|
515 |
-
|
516 |
-
function handleLogout() { /* ... identique ... */ if (!auth) { console.error("Auth missing for logout"); return; } console.log("Déconnexion..."); auth.signOut().catch(error => { console.error("Logout Error:", error); alert("Erreur lors de la déconnexion."); }); }
|
517 |
-
|
518 |
-
function authSwitchMode() {
|
519 |
-
isLoginMode = !isLoginMode;
|
520 |
-
clearMessage(); // **MODIFIÉ**
|
521 |
-
if (authEmailInput) authEmailInput.value = '';
|
522 |
-
if (authPasswordInput) authPasswordInput.value = '';
|
523 |
-
if (authTitle) authTitle.textContent = isLoginMode ? 'Connexion' : 'Inscription';
|
524 |
-
if (authActionButton) authActionButton.textContent = isLoginMode ? 'Se Connecter' : 'S\'inscrire';
|
525 |
-
if (authSwitchText) authSwitchText.textContent = isLoginMode ? 'Pas encore de compte ?' : 'Déjà un compte ?';
|
526 |
-
if (authSwitchLink) authSwitchLink.textContent = isLoginMode ? 'Inscrivez-vous ici' : 'Connectez-vous ici';
|
527 |
-
if (authEmailInput) authEmailInput.focus();
|
528 |
-
}
|
529 |
-
|
530 |
-
function handleAuthError(error) {
|
531 |
-
console.error("Erreur Auth Firebase:", error.code, error.message);
|
532 |
-
// Utilise la fonction showMessage pour afficher l'erreur
|
533 |
-
showMessage(getAuthErrorMessage(error), true); // true indique que c'est une erreur (rouge)
|
534 |
}
|
535 |
|
536 |
-
function
|
537 |
-
|
538 |
-
|
539 |
-
function
|
540 |
-
function
|
541 |
-
function
|
542 |
-
function
|
543 |
-
function
|
544 |
-
function
|
545 |
-
function
|
546 |
-
function
|
547 |
-
function
|
548 |
-
function
|
549 |
-
function handleAddNewExercise() { addExercise(true); }
|
550 |
-
function addExercise(navigateToNew = false) { if (!currentFirebaseUser || !exercisesContainer) { console.error("Cannot add exercise"); 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" required> <button class="btn btn-danger remove-exercise" style="padding: 0.3rem 0.6rem; flex-shrink: 0;" aria-label="Supprimer exercice">×</button> </div> <div class="form-group" style="margin: 0.5rem 0;"> <label style="display: inline-flex; align-items: center; color: var(--text-secondary); font-size: 0.9rem; font-weight: normal;"> <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: 1rem; 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.findIndex(el => el === exerciseDiv); if (indexToRemove > -1) { if (confirm("Supprimer cet exercice et toutes ses séries ?")) { exerciseDiv.remove(); workoutExercisesForm.splice(indexToRemove, 1); if (currentExerciseIndex >= indexToRemove && currentExerciseIndex > 0) { currentExerciseIndex--; } 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(); const nameInput = exerciseDiv.querySelector('.exercise-name'); if (nameInput) nameInput.focus(); } else { updateExerciseNavButtons(); } }
|
551 |
-
function addSeries(container) { if (!currentFirebaseUser || !container) { console.error("Cannot add series"); 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 style="font-weight: normal; font-size: 0.8rem;">Reps</label> <input type="number" class="reps" min="1" placeholder="10" style="padding: 0.5rem;" required> </div> <div class="form-group" style="flex: 1.2;"> <label style="font-weight: normal; font-size: 0.8rem;">Charge (kg)</label> <input type="number" class="weight" min="0" step="any" placeholder="20" style="padding: 0.5rem;" required> </div> <div class="form-group" style="flex: 1; display: flex; align-items: center; padding-bottom: 0.8rem; min-width: 100px;"> <label style="display: inline-flex; align-items: center; color: var(--text-secondary); font-size: 0.8rem; margin-bottom: 0; white-space: nowrap; font-weight: normal;"> <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.8rem; flex-basis: 30px; flex-grow: 0; align-self: center;" aria-label="Supprimer série">×</button> </div>`; container.appendChild(seriesDiv); const removeBtn = seriesDiv.querySelector('.remove-series'); removeBtn.addEventListener('click', () => { seriesDiv.remove(); }); }
|
552 |
-
function navigateExerciseForm(direction) { if (!exercisesContainer) return; workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); const newIndex = currentExerciseIndex + direction; if (newIndex >= 0 && newIndex < workoutExercisesForm.length) { currentExerciseIndex = newIndex; renderActiveExerciseForm(); } }
|
553 |
-
function renderActiveExerciseForm() { if (!exercisesContainer) return; workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); workoutExercisesForm.forEach((ex, index) => { if (index === currentExerciseIndex) { ex.classList.add('active-exercise'); ex.style.display = 'block'; } else { ex.classList.remove('active-exercise'); ex.style.display = 'none'; } }); updateExerciseNavButtons(); }
|
554 |
-
function updateExerciseNavButtons() { if (!currentExerciseIndicator || !prevExerciseBtn || !nextExerciseBtn) { console.warn("Exercise nav elements missing"); return; } 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); } }
|
555 |
-
function clearNewWorkoutForm(typeName = "Libre") { console.log("Nettoyage formulaire pour type:", typeName); if (!currentFirebaseUser) { console.error("Cannot clear form: No user"); return; } selectedWorkoutTypeName = typeName; const workoutNameInput = document.getElementById('workout-name'); if (workoutNameInput) workoutNameInput.value = (typeName === "Libre" ? "" : typeName); if (workoutDateInput) setTodayDate(); const durationInput = document.getElementById('workout-duration'); if (durationInput) durationInput.value = '60'; if (exercisesContainer) exercisesContainer.innerHTML = ''; workoutExercisesForm = []; if (satisfactionRange) satisfactionRange.value = 75; if (satisfactionValue) satisfactionValue.textContent = '75%'; currentWorkoutId = null; currentExerciseIndex = 0; addExercise(false); updateWorkoutTypeIndicator(); renderActiveExerciseForm(); }
|
556 |
-
function updateWorkoutTypeIndicator() { if (!workoutTypeIndicator) return; if (selectedWorkoutTypeName && selectedWorkoutTypeName !== "Libre") { workoutTypeIndicator.textContent = selectedWorkoutTypeName; workoutTypeIndicator.classList.remove('hidden'); } else { workoutTypeIndicator.classList.add('hidden'); } }
|
557 |
-
function renderWorkoutsList() { if (!workoutsList || !emptyWorkoutMessage) { console.warn("Cannot render workout list: DOM elements missing."); return; } workoutsList.innerHTML = ''; if (!workouts || 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 => { if (!workout || !workout.id) { console.warn("Skipping invalid workout data:", workout); return; } const workoutDate = workout.date ? new Date(workout.date).toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric' }) : 'Date inconnue'; 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" style="margin-left: 8px;">${workout.workoutTypeName}</span>` : ''; const exercisesCount = (workout.exercises || []).length; workoutDiv.innerHTML = `<div class="workout-header"><h3 style="margin-bottom: 0.5rem;">${workout.name || 'Séance sans nom'} ${typeBadge}</h3><div class="badge">${workoutDate}</div></div><div class="workout-details"><span>${workout.duration || '?'} min</span> | <span>${exercisesCount} exo${exercisesCount > 1 ? 's' : ''}</span> | <span>${(workout.totalTonnage || 0).toFixed(1)} kg</span> | <span>${workout.satisfaction || '?'}%</span></div>`; workoutDiv.addEventListener('click', () => displayWorkoutDetails(workout.id)); workoutsList.appendChild(workoutDiv); }); } }
|
558 |
-
function displayWorkoutDetails(workoutId) { if (!currentFirebaseUser) { console.error("User not logged in for details"); return; } const workout = workouts.find(w => w && w.id === workoutId); if (!workout) { alert("Détails de la séance introuvables."); showPage('home-page'); return; } console.log("Affichage détails pour:", workoutId, workout); const detailNameEl = document.getElementById('detail-workout-name'); const detailDateEl = document.getElementById('detail-date'); const detailDurationEl = document.getElementById('detail-duration'); const detailTonnageEl = document.getElementById('detail-tonnage'); const detailSatisfactionEl = document.getElementById('detail-satisfaction'); const detailExercisesCountEl = document.getElementById('detail-exercises-count'); const detailExercisesContainer = document.getElementById('detail-exercises-container'); const detailWorkoutTypeBadge = document.getElementById('detail-workout-type'); if (detailWorkoutTypeBadge) { if (workout.workoutTypeName && workout.workoutTypeName !== "Libre") { detailWorkoutTypeBadge.textContent = workout.workoutTypeName; detailWorkoutTypeBadge.classList.remove('hidden'); } else { detailWorkoutTypeBadge.classList.add('hidden'); } } if (detailNameEl) detailNameEl.textContent = workout.name || 'Séance sans nom'; if (detailDateEl) detailDateEl.textContent = workout.date ? new Date(workout.date).toLocaleDateString('fr-FR') : 'Inconnue'; if (detailDurationEl) detailDurationEl.textContent = workout.duration || '?'; if (detailTonnageEl) detailTonnageEl.textContent = (workout.totalTonnage || 0).toFixed(1); if (detailSatisfactionEl) detailSatisfactionEl.textContent = `${workout.satisfaction || '?'}%`; const exercisesCount = (workout.exercises || []).length; if (detailExercisesCountEl) detailExercisesCountEl.textContent = exercisesCount; if (detailExercisesContainer) { detailExercisesContainer.innerHTML = ''; if (exercisesCount > 0) { workout.exercises.forEach((exercise) => { if (!exercise) return; 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(--info); color: var(--text-light);">Dégr.</span>' : ''; const weightFactor = exercise.isUnilateral ? 2 : 1; const seriesTonnage = (serie.reps || 0) * (serie.weight || 0) * 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: var(--text-secondary);">(${seriesTonnage.toFixed(1)} kg)</span></div></div>`; }); const unilateralLabel = exercise.isUnilateral ? ' <span class="badge" style="font-size: 0.7rem; background-color: var(--info); color: var(--text-light);">Unilat.</span>' : ''; exerciseDiv.innerHTML = `<h4 style="font-size: 1.1rem; margin-bottom: 0.8rem; color: var(--text-light); text-transform: none; font-family: var(--font-primary); letter-spacing: normal;">${exercise.name || 'Exercice sans nom'}${unilateralLabel}</h4> <div class="series-summary-container"> ${seriesHtml} </div>`; detailExercisesContainer.appendChild(exerciseDiv); }); } else { detailExercisesContainer.innerHTML = '<p style="color: var(--text-secondary); text-align: center;">Aucun exercice enregistré.</p>'; } } currentWorkoutId = workoutId; showPage('workout-details-page'); }
|
559 |
-
function updateStats() { console.log("MàJ stats..."); if (!currentFirebaseUser || !statsWorkoutCountEl || !statsAvgTonnageEl || !statsAvgSatisfactionEl || !typeTrendsContainer || !noTrendsMessage) { console.warn("Cannot update stats: Missing elements."); if(statsWorkoutCountEl) statsWorkoutCountEl.textContent = '0'; if(statsAvgTonnageEl) statsAvgTonnageEl.textContent = '0'; if(statsAvgSatisfactionEl) statsAvgSatisfactionEl.textContent = '0%'; if(typeTrendsContainer) typeTrendsContainer.innerHTML = ''; if(noTrendsMessage) noTrendsMessage.classList.remove('hidden'); return; } const workoutCount = workouts.length; statsWorkoutCountEl.textContent = workoutCount; if (workoutCount === 0) { statsAvgTonnageEl.textContent = '0'; statsAvgSatisfactionEl.textContent = '0%'; renderTypeTrends({}); return; } const totalTonnageAll = workouts.reduce((sum, w) => sum + (w.totalTonnage || 0), 0); statsAvgTonnageEl.textContent = workoutCount > 0 ? (totalTonnageAll / workoutCount).toFixed(1) : '0'; const totalSatisfaction = workouts.reduce((sum, w) => sum + (w.satisfaction || 0), 0); statsAvgSatisfactionEl.textContent = workoutCount > 0 ? `${Math.round(totalSatisfaction / workoutCount)}%` : '0%'; console.log("Calcul tendances..."); const trendsData = {}; const structuredWorkouts = workouts.filter(w => w && w.workoutTypeName && w.workoutTypeName !== "Libre"); const groupedByType = structuredWorkouts.reduce((acc, workout) => { const type = workout.workoutTypeName; if (!acc[type]) acc[type] = []; acc[type].push(workout); return acc; }, {}); for (const typeName in groupedByType) { const sessionsOfType = groupedByType[typeName].sort((a, b) => new Date(a.date) - new Date(b.date)); trendsData[typeName] = sessionsOfType.slice(-3).map(s => ({ date: s.date, tonnage: s.totalTonnage || 0 })); } console.log("Données tendances:", trendsData); renderTypeTrends(trendsData); }
|
560 |
-
function renderTypeTrends(trends) { if (!typeTrendsContainer || !noTrendsMessage || !trendsSpinner) { console.error("Cannot render trends: Missing DOM elements."); return;} trendsSpinner.classList.add('hidden'); typeTrendsContainer.innerHTML = ''; const trendTypes = Object.keys(trends); if (trendTypes.length === 0) { noTrendsMessage.classList.remove('hidden'); } else { noTrendsMessage.classList.add('hidden'); trendTypes.sort().forEach(typeName => { const typeData = trends[typeName]; if (!typeData || typeData.length === 0) return; const card = document.createElement('div'); card.className = 'card type-trend-card'; let listItems = ''; typeData.forEach(session => { const formattedDate = session.date ? new Date(session.date).toLocaleDateString('fr-FR', { day: '2-digit', month: 'short' }) : '??'; const tonnageText = (session.tonnage !== undefined && session.tonnage !== null) ? `${session.tonnage.toFixed(1)} kg` : 'N/A'; listItems += `<li><span>${formattedDate}</span> <strong>${tonnageText}</strong></li>`; }); card.innerHTML = `<h4>${typeName} (3 Dernières)</h4><ul>${listItems}</ul>`; typeTrendsContainer.appendChild(card); }); } }
|
561 |
-
function handleFirestoreError(error) { console.error("Erreur Firestore:", error.code, error.message); alert(`Erreur de base de données: ${error.message}`); }
|
562 |
-
|
563 |
|
564 |
// --- ÉCOUTEURS D'ÉVÉNEMENTS ---
|
565 |
function initEventListeners() {
|
566 |
console.log("initEventListeners: Attachement...");
|
|
|
|
|
567 |
// Auth
|
568 |
if (loginForm) loginForm.addEventListener('submit', handleAuthAction); else console.error("!loginForm");
|
569 |
if (authSwitchLink) authSwitchLink.addEventListener('click', authSwitchMode); else console.error("!authSwitchLink");
|
570 |
-
if (forgotPasswordLink) forgotPasswordLink.addEventListener('click', handlePasswordReset); else console.error("!forgotPasswordLink");
|
571 |
if (logoutBtn) logoutBtn.addEventListener('click', handleLogout); else console.error("!logoutBtn");
|
572 |
// Nav
|
573 |
navItems.forEach((item) => { if (item) item.addEventListener('click', (e) => { e.preventDefault(); const targetPageId = item.getAttribute('data-page'); if (!currentFirebaseUser && targetPageId !== 'login-page') { showLoginPage(); return; } if (targetPageId) showPage(targetPageId); }); else console.error(`!navItem`); });
|
@@ -580,10 +538,11 @@
|
|
580 |
if (selectWorkoutTypeDropdown) selectWorkoutTypeDropdown.addEventListener('change', updateStartStructuredBtnState); else console.error("!selectWorkoutTypeDropdown");
|
581 |
if (startStructuredWorkoutBtn) startStructuredWorkoutBtn.addEventListener('click', () => { if (selectWorkoutTypeDropdown) { const type = selectWorkoutTypeDropdown.value; if (type) { clearNewWorkoutForm(type); showPage('new-workout-page'); } else { alert("Veuillez sélectionner un type de séance."); } } }); else console.error("!startStructuredWorkoutBtn");
|
582 |
if (startFreeWorkoutBtn) startFreeWorkoutBtn.addEventListener('click', () => { clearNewWorkoutForm("Libre"); showPage('new-workout-page'); }); else console.error("!startFreeWorkoutBtn");
|
583 |
-
// General Nav
|
584 |
-
|
585 |
// New Workout Actions
|
586 |
-
if (cancelNewWorkoutBtn) cancelNewWorkoutBtn.addEventListener('click', () => { if (confirm("Annuler cette séance ? Les données non enregistrées seront perdues.")) showPage('
|
|
|
587 |
if (saveWorkoutBtn) saveWorkoutBtn.addEventListener('click', saveWorkout); else console.error("!saveWorkoutBtn");
|
588 |
if (addExerciseBtn) addExerciseBtn.addEventListener('click', handleAddNewExercise); else console.error("!addExerciseBtn");
|
589 |
// Exercise Nav
|
@@ -602,31 +561,14 @@
|
|
602 |
|
603 |
// --- INITIALISATION ---
|
604 |
document.addEventListener('DOMContentLoaded', () => {
|
605 |
-
console.log("DOM chargé. Initialisation
|
606 |
-
if (typeof firebase !== 'undefined' &&
|
607 |
-
|
608 |
-
|
609 |
-
|
610 |
-
console.log("Firebase prêt. Initialisation des listeners...");
|
611 |
-
initEventListeners(); // Initialize event listeners first
|
612 |
-
initAuthListener(); // Initialize auth listener to handle state changes and initial check
|
613 |
-
const user = auth.currentUser; // Check initial state synchronously IF possible
|
614 |
-
if (user) {
|
615 |
-
console.log("Utilisateur déjà connecté trouvé:", user.uid);
|
616 |
-
currentFirebaseUser = user;
|
617 |
-
showApp();
|
618 |
-
} else {
|
619 |
-
console.log("Utilisateur non connecté au chargement.");
|
620 |
-
showLoginPage();
|
621 |
-
}
|
622 |
-
} else {
|
623 |
-
console.error("ERREUR CRITIQUE: Firebase Auth ou Firestore non initialisé.");
|
624 |
-
alert("Erreur critique : Services Firebase non disponibles.");
|
625 |
-
}
|
626 |
} else {
|
627 |
-
console.error("ERREUR CRITIQUE: Firebase
|
628 |
-
|
629 |
-
document.body.innerHTML = '<div style="color: red; padding: 30px; text-align: center; font-family: sans-serif; background-color: #333;"><h1>Erreur Critique</h1><p>Impossible de charger les composants nécessaires. Vérifiez votre connexion et la console (F12).</p></div>';
|
630 |
}
|
631 |
}); // FIN DOMContentLoaded
|
632 |
|
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
6 |
<title>Cocktail Fitness - Suivi</title>
|
7 |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script>
|
8 |
|
9 |
+
<!-- SDK Firebase (v8) -->
|
10 |
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
|
11 |
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
|
12 |
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
|
13 |
|
14 |
+
<!-- Google Fonts -->
|
15 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
16 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
17 |
<link href="https://fonts.googleapis.com/css2?family=Oswald:wght@700&family=Roboto:wght@400;700&display=swap" rel="stylesheet">
|
18 |
|
19 |
|
20 |
<style>
|
21 |
+
/* Variables & Styles Généraux (inchangés) */
|
22 |
:root {
|
23 |
+
--bg-dark: #101010; --bg-card: #1e1e1e; --text-light: #f0f0f0; --text-secondary: #aaaaaa; --accent: #FFC107; --accent-dark: #E0A800; --text-on-accent: #101010; --neutral-white: #FFFFFF; --danger: #f44336; --info: #6c757d; --success: #4CAF50; --border-color: #333333;
|
24 |
+
--font-primary: 'Roboto', sans-serif; --font-headings: 'Oswald', sans-serif;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
}
|
|
|
|
|
26 |
* { margin: 0; padding: 0; box-sizing: border-box; font-family: var(--font-primary); }
|
27 |
body { background-color: var(--bg-dark); color: var(--text-light); min-height: 100vh; padding-bottom: 80px; }
|
28 |
.container { width: 100%; max-width: 800px; margin: 0 auto; padding: 1rem; }
|
|
|
|
|
29 |
header { padding: 1rem 0; text-align: center; border-bottom: 1px solid var(--border-color); margin-bottom: 1rem; display: flex; flex-direction: column; align-items: center; }
|
30 |
+
/* Style pour rendre le logo cliquable */
|
31 |
+
#logo-link { display: inline-block; cursor: pointer; text-decoration: none; margin-bottom: 0.5rem; }
|
32 |
+
header img#app-logo { max-width: 250px; height: auto; display: block; /* Assure que l'image est bien un bloc dans le lien */ }
|
33 |
header p { color: var(--text-secondary); font-size: 0.9rem; }
|
|
|
|
|
34 |
h1, h2, h3, h4 { font-family: var(--font-headings); font-weight: 700; color: var(--accent); text-transform: uppercase; letter-spacing: 0.5px; }
|
|
|
|
|
35 |
.btn { background-color: var(--accent); color: var(--text-on-accent); border: none; padding: 0.6rem 1.2rem; border-radius: 4px; cursor: pointer; font-weight: bold; font-family: var(--font-headings); text-transform: uppercase; transition: background-color 0.2s, color 0.2s; }
|
36 |
.btn:hover { background-color: var(--accent-dark); color: var(--text-on-accent); }
|
37 |
.btn:disabled { background-color: #555; color: #999; cursor: not-allowed; }
|
|
|
42 |
.btn-danger:hover { background-color: #d32f2f; color: white; }
|
43 |
.btn-info { background-color: var(--info); color: var(--text-light); border-color: var(--info); }
|
44 |
.btn-info:hover { background-color: #5a6268; color: var(--text-light); }
|
|
|
|
|
45 |
input, select, textarea { width: 100%; padding: 0.7rem; margin-bottom: 1rem; background-color: #2a2a2a; border: 1px solid #444; border-radius: 4px; color: var(--text-light); font-size: 1rem; }
|
46 |
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.3); }
|
47 |
input[type="checkbox"] { width: auto; margin-right: 0.5rem; vertical-align: middle; accent-color: var(--accent); }
|
|
|
49 |
.form-group { margin-bottom: 1rem; }
|
50 |
.form-row { display: flex; gap: 1rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
|
51 |
.form-row > * { flex: 1; margin-bottom: 0; min-width: 100px; }
|
|
|
|
|
52 |
.card { background-color: var(--bg-card); border-radius: 8px; padding: 1.2rem; margin-bottom: 1rem; box-shadow: 0 4px 8px rgba(0,0,0,0.3); }
|
|
|
|
|
53 |
.exercise { border-left: 4px solid var(--accent); padding-left: 1rem; margin-bottom: 1.5rem; display: none; background-color: rgba(0,0,0,0.1); padding: 1rem; border-radius: 0 4px 4px 0; }
|
54 |
.exercise.active-exercise { display: block; animation: fadeIn 0.3s ease-in-out; }
|
55 |
.exercise-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; gap: 0.5rem;}
|
56 |
.exercise-header input { margin-bottom: 0; }
|
57 |
.series-container { margin-left: 0rem; margin-top: 1rem; }
|
58 |
.series { background-color: #282828; padding: 0.8rem; border-radius: 4px; margin-bottom: 0.8rem; border: 1px solid #333; }
|
|
|
|
|
59 |
.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 -3px 10px rgba(0,0,0,0.4); z-index: 10; }
|
60 |
.nav-item { text-align: center; color: var(--text-secondary); text-decoration: none; font-size: 0.85rem; transition: color 0.2s; padding: 0 0.5rem; }
|
61 |
.nav-item.active { color: var(--accent); font-weight: bold; }
|
62 |
.nav-icon { font-size: 1.5rem; margin-bottom: 0.2rem; }
|
|
|
|
|
63 |
.workout-card { border-left: 4px solid var(--accent); cursor: pointer; transition: transform 0.2s ease-out, box-shadow 0.2s ease-out; }
|
64 |
.workout-card:hover { transform: translateY(-3px); box-shadow: 0 6px 12px rgba(0,0,0,0.4); }
|
65 |
.workout-header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem; }
|
|
|
68 |
.workout-details { font-size: 0.9rem; color: var(--text-secondary); margin-top: 0.8rem; }
|
69 |
.workout-details span { margin-right: 0.8rem; }
|
70 |
.workout-header h3 .badge.type-badge { background-color: var(--neutral-white); color: var(--bg-dark); padding: 0.3rem 0.6rem; border-radius: 12px; font-size: 0.8rem; font-weight: bold; white-space: nowrap; margin-left: 5px; position: static; flex-shrink: initial; margin-left: 5px; }
|
|
|
|
|
71 |
.stat-card { text-align: center; padding: 1rem; }
|
72 |
.stat-value { font-size: 2rem; color: var(--accent); font-weight: bold; font-family: var(--font-headings); margin-bottom: 0.3rem; }
|
73 |
.stat-card div:last-child { color: var(--text-secondary); font-size: 0.9rem; }
|
74 |
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1rem; }
|
|
|
|
|
75 |
.hidden { display: none !important; }
|
76 |
#app-container > div:not(.active) { display: none !important; }
|
77 |
.flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; }
|
|
|
|
|
78 |
.badge { background-color: var(--accent); color: var(--text-on-accent); padding: 0.3rem 0.6rem; border-radius: 12px; font-size: 0.8rem; font-weight: bold; white-space: nowrap; margin-left: 5px; }
|
|
|
|
|
|
|
79 |
.spinner { border: 4px solid rgba(255, 255, 255, 0.1); width: 36px; height: 36px; border-radius: 50%; border-left-color: var(--accent); animation: spin 1s linear infinite; margin: 2rem auto; display: none; }
|
80 |
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
|
|
|
|
81 |
.exercise-summary { margin: 0.3rem 0; padding: 0.5rem 0; border-bottom: 1px solid var(--border-color); font-size: 0.9rem; }
|
82 |
.exercise-summary:last-child { border-bottom: none; }
|
83 |
.series-summary-container .badge { font-size: 0.7rem; padding: 0.2rem 0.4rem; background-color: var(--info); color: var(--text-light); }
|
|
|
|
|
84 |
.satisfaction { display: flex; align-items: center; justify-content: center; flex-direction: column; margin-top: 1rem; }
|
85 |
input[type="range"] { accent-color: var(--accent); cursor: pointer;}
|
86 |
.satisfaction-value { font-size: 2.2rem; color: var(--accent); margin-top: 0.5rem; font-weight: bold; font-family: var(--font-headings); }
|
|
|
|
|
87 |
.exercise-navigation { display: flex; justify-content: space-between; margin-top: 1.5rem; margin-bottom: 1.5rem; align-items: center;}
|
88 |
.exercise-navigation span { color: var(--text-secondary); font-style: italic; }
|
|
|
|
|
89 |
#login-page .card { max-width: 400px; margin: 3rem auto; }
|
90 |
#login-page h2 { text-align: center; margin-bottom: 1.5rem; color: var(--accent); }
|
|
|
91 |
#login-message { text-align: center; margin-top: 1rem; font-size: 0.9rem; min-height: 1.2em; font-weight: bold; display: none; }
|
92 |
#login-message.error { color: var(--danger); }
|
93 |
#login-message.success { color: var(--success); }
|
94 |
#login-form button { margin-top: 1rem; }
|
95 |
+
.auth-options { margin-top: 1.5rem; font-size: 0.9rem; text-align: center; }
|
96 |
+
.auth-switch { margin-bottom: 0.5rem; color: var(--text-secondary); }
|
97 |
.auth-options a { color: var(--accent); cursor: pointer; text-decoration: underline; font-weight: bold; }
|
98 |
+
#forgot-password-link { display: block; }
|
|
|
|
|
|
|
99 |
.user-info { text-align: right; margin-bottom: 1rem; font-size: 0.9rem; color: var(--text-secondary);}
|
100 |
.user-info strong { color: var(--text-light); margin: 0 5px; }
|
101 |
.user-info button { margin-left: 0.8rem; padding: 0.3rem 0.7rem; font-size: 0.8rem; }
|
|
|
|
|
102 |
#workout-types-list li { display: flex; justify-content: space-between; align-items: center; background-color: #2a2a2a; padding: 0.7rem 1rem; margin-bottom: 0.5rem; border-radius: 4px; border-left: 4px solid var(--neutral-white); color: var(--text-light); font-weight: bold; }
|
103 |
#add-type-form { display: flex; gap: 0.5rem; align-items: flex-end;}
|
104 |
#add-type-form input { margin-bottom: 0; flex-grow: 1;}
|
105 |
#add-type-form button { flex-shrink: 0; }
|
106 |
#add-type-error { color: var(--danger); font-size: 0.9rem; margin-top: 0.5rem; font-weight: bold; display: none; }
|
|
|
|
|
107 |
#select-workout-type-page .card { text-align: center; }
|
108 |
#select-workout-type-page .btn { margin-top: 1rem; width: 80%; }
|
109 |
#select-workout-type { margin-bottom: 1rem;}
|
|
|
|
|
110 |
.type-trend-card { margin-bottom: 1.5rem; }
|
111 |
.type-trend-card h4 { color: var(--neutral-white); border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; margin-bottom: 1rem; text-transform: uppercase; }
|
112 |
.type-trend-card ul { list-style: none; padding: 0; font-size: 0.95rem; }
|
|
|
114 |
.type-trend-card li:last-child { border-bottom: none; }
|
115 |
.type-trend-card li span:first-child { color: var(--text-secondary); font-size: 0.85rem; margin-right: 1rem; }
|
116 |
.type-trend-card li strong { color: var(--accent); }
|
117 |
+
/* Style simple pour la page d'accueil */
|
118 |
+
#accueil-page .card p { line-height: 1.6; color: var(--text-light); }
|
119 |
+
#accueil-page .card ul { list-style: disc; padding-left: 25px; margin-top: 1rem; color: var(--text-light); }
|
120 |
+
#accueil-page .card li { margin-bottom: 0.5rem; }
|
121 |
|
122 |
/* Media Queries */
|
123 |
@media (max-width: 600px) {
|
|
|
135 |
#add-type-form { flex-direction: column; align-items: stretch;}
|
136 |
header img#app-logo { max-width: 200px; }
|
137 |
}
|
|
|
138 |
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
|
|
139 |
</style>
|
140 |
</head>
|
141 |
<body>
|
142 |
<div class="container">
|
143 |
+
<!-- Header avec logo cliquable -->
|
144 |
<header>
|
145 |
+
<a href="#" id="logo-link">
|
146 |
+
<img src="input_file_0.png" alt="Cocktail Fitness Logo" id="app-logo">
|
147 |
+
</a>
|
148 |
<p>Votre suivi de séances personnalisé</p>
|
149 |
</header>
|
150 |
|
|
|
155 |
<form id="login-form" novalidate>
|
156 |
<div class="form-group"> <label for="auth-email">Email</label> <input type="email" id="auth-email" placeholder="[email protected]" required> </div>
|
157 |
<div class="form-group"> <label for="auth-password">Mot de passe</label> <input type="password" id="auth-password" placeholder="********" required> </div>
|
|
|
158 |
<p id="login-message" style="display: none;"></p>
|
159 |
<button type="submit" id="auth-action-btn" class="btn" style="width: 100%;">Se Connecter</button>
|
160 |
</form>
|
|
|
161 |
<div class="auth-options">
|
162 |
<div class="auth-switch">
|
163 |
<span id="auth-switch-text">Pas encore de compte ?</span>
|
164 |
<a id="auth-switch-link">Inscrivez-vous ici</a>
|
165 |
</div>
|
|
|
166 |
<a href="#" id="forgot-password-link">Mot de passe oublié ?</a>
|
167 |
</div>
|
168 |
</div>
|
|
|
176 |
</div>
|
177 |
|
178 |
<div id="app-container">
|
179 |
+
<!-- *NOUVEAU* Accueil Page -->
|
180 |
+
<div id="accueil-page">
|
181 |
+
<h2>Bienvenue !</h2>
|
182 |
+
<div class="card">
|
183 |
+
<p>
|
184 |
+
Bienvenue sur l'application de suivi de **Cocktail Fitness** !
|
185 |
+
</p>
|
186 |
+
<p style="margin-top: 1rem;">
|
187 |
+
Utilisez cette application pour enregistrer vos séances d'entraînement,
|
188 |
+
suivre votre progression et analyser vos performances au fil du temps.
|
189 |
+
</p>
|
190 |
+
<ul style="margin-top: 1rem;">
|
191 |
+
<li>Cliquez sur "Séances" pour voir vos entraînements passés ou en créer un nouveau.</li>
|
192 |
+
<li>Cliquez sur "Stats" pour visualiser vos statistiques globales.</li>
|
193 |
+
<li>Gérez vos types de séances personnalisés (Push, Pull, Legs...).</li>
|
194 |
+
</ul>
|
195 |
+
<p style="margin-top: 1.5rem; text-align: center;">
|
196 |
+
Bon entraînement ! 💪
|
197 |
+
</p>
|
198 |
+
</div>
|
199 |
+
</div>
|
200 |
+
|
201 |
+
<!-- *RENOMMÉ* Séances Page (Anciennement home-page) -->
|
202 |
+
<div id="seances-page">
|
203 |
<div class="flex-between">
|
204 |
<h2>Mes séances</h2>
|
205 |
<div>
|
|
|
217 |
<div id="manage-types-page">
|
218 |
<div class="flex-between">
|
219 |
<h2>Gérer les Types</h2>
|
220 |
+
<!-- Bouton retour mène maintenant à la page des séances -->
|
221 |
+
<button class="btn btn-outline back-to-seances-btn">Retour</button>
|
222 |
</div>
|
223 |
<div class="card">
|
224 |
<h3>Types Existants</h3>
|
|
|
241 |
<div id="select-workout-type-page">
|
242 |
<div class="flex-between">
|
243 |
<h2>Démarrer Séance</h2>
|
244 |
+
<!-- Bouton retour mène maintenant à la page des séances -->
|
245 |
+
<button class="btn btn-outline back-to-seances-btn">Annuler</button>
|
246 |
</div>
|
247 |
<div class="card">
|
248 |
<h3>Séance Structurée</h3>
|
|
|
294 |
<div id="workout-details-page">
|
295 |
<div class="flex-between">
|
296 |
<h2>Détail Séance <span id="detail-workout-type" class="badge type-badge hidden"></span></h2>
|
297 |
+
<!-- Bouton retour mène maintenant à la page des séances -->
|
298 |
+
<button class="btn btn-outline back-to-seances-btn">Retour</button>
|
299 |
</div>
|
300 |
<div class="card">
|
301 |
<div class="workout-details-info">
|
|
|
334 |
|
335 |
</div> <!-- Fin #app-container -->
|
336 |
|
337 |
+
<!-- Bottom Navigation - MISE A JOUR -->
|
338 |
<nav class="nav-bottom">
|
339 |
+
<!-- NOUVEAU lien Accueil -->
|
340 |
+
<a href="#" class="nav-item" data-page="accueil-page"> <div class="nav-icon">🏠</div> <div>Accueil</div> </a>
|
341 |
+
<!-- Lien Séances (anciennement Home) -->
|
342 |
+
<a href="#" class="nav-item" data-page="seances-page"> <div class="nav-icon">📋</div> <div>Séances</div> </a>
|
343 |
+
<!-- Lien Stats (inchangé) -->
|
344 |
<a href="#" class="nav-item" data-page="stats-page"> <div class="nav-icon">📊</div> <div>Stats</div> </a>
|
345 |
</nav>
|
346 |
|
|
|
354 |
// Firebase Init
|
355 |
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" };
|
356 |
let app, auth, db;
|
357 |
+
try { app = firebase.initializeApp(firebaseConfig); auth = firebase.auth(); db = firebase.firestore(); console.log("Firebase Initialisé !"); }
|
358 |
+
catch (e) { console.error("Erreur Init Firebase:", e); alert("Erreur critique: Impossible d'initialiser Firebase."); document.body.innerHTML = '<div style="color: red; padding: 20px;">Erreur critique: Impossible d\'initialiser Firebase. Vérifiez la configuration et la connexion.</div>'; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
359 |
|
360 |
// --- Variables d'État ---
|
361 |
let workouts = []; let currentWorkoutId = null; let currentExerciseIndex = 0; let workoutExercisesForm = []; let currentFirebaseUser = null; let userWorkoutTypes = []; let selectedWorkoutTypeName = null; let isLoginMode = true;
|
|
|
368 |
const loginForm = document.getElementById('login-form');
|
369 |
const authActionButton = document.getElementById('auth-action-btn');
|
370 |
const authSwitchLink = document.getElementById('auth-switch-link');
|
371 |
+
const forgotPasswordLink = document.getElementById('forgot-password-link');
|
372 |
+
const logoLink = document.getElementById('logo-link'); // **NOUVEAU**
|
373 |
const authTitle = document.getElementById('auth-title');
|
374 |
const authSwitchText = document.getElementById('auth-switch-text');
|
375 |
+
const loginMessage = document.getElementById('login-message');
|
376 |
const currentUserDisplay = document.getElementById('current-user-display');
|
377 |
const logoutBtn = document.getElementById('logout-btn');
|
378 |
const spinner = document.querySelector('#workouts-list .spinner');
|
|
|
384 |
const addExerciseBtn = document.getElementById('add-exercise-btn');
|
385 |
const exercisesContainer = document.getElementById('exercises-container');
|
386 |
const workoutsList = document.getElementById('workouts-list');
|
387 |
+
const backToSeancesBtns = document.querySelectorAll('.back-to-seances-btn'); // **RENOMMÉ**
|
388 |
const deleteWorkoutBtn = document.getElementById('delete-workout-btn');
|
389 |
const satisfactionRange = document.getElementById('satisfaction');
|
390 |
const satisfactionValue = document.querySelector('.satisfaction-value');
|
|
|
413 |
const typeTrendsContainer = document.getElementById('type-trends-container');
|
414 |
const noTrendsMessage = document.getElementById('no-trends-message');
|
415 |
const trendsSpinner = document.getElementById('trends-spinner');
|
416 |
+
// On garde une référence à la page d'accueil et aux séances pour clarté
|
417 |
+
const accueilPage = document.getElementById('accueil-page');
|
418 |
+
const seancesPage = document.getElementById('seances-page');
|
419 |
|
420 |
// ===========================================
|
421 |
// --- DÉFINITION DES FONCTIONS ---
|
422 |
// ===========================================
|
423 |
|
424 |
+
function showMessage(message, isError = true) { /* ... inchangé ... */ if (loginMessage) { loginMessage.textContent = message; loginMessage.className = isError ? 'error' : 'success'; loginMessage.style.display = 'block'; } }
|
425 |
+
function clearMessage() { /* ... inchangé ... */ if (loginMessage) { loginMessage.textContent = ''; loginMessage.style.display = 'none'; } }
|
426 |
+
|
427 |
+
function initAuthListener() { if (!auth) return; auth.onAuthStateChanged(user => { console.log("Auth state changed:", user ? user.uid : 'null'); currentFirebaseUser = user; if (user) { showApp(); } else { workouts = []; userWorkoutTypes = []; showLoginPage(); } }); }
|
428 |
+
function showLoginPage() { /* ... inchangé ... */ if(loginPage) loginPage.style.display = 'block'; if(mainAppContent) mainAppContent.style.display = 'none'; isLoginMode = true; authSwitchMode(); }
|
429 |
+
|
430 |
+
function showApp() {
|
431 |
+
if (!currentFirebaseUser || !mainAppContent) { showLoginPage(); return; }
|
432 |
+
console.log("Affichage App for user:", currentFirebaseUser.email);
|
433 |
+
if(loginPage) loginPage.style.display = 'none';
|
434 |
+
mainAppContent.style.display = 'block';
|
435 |
+
if(currentUserDisplay) currentUserDisplay.textContent = currentFirebaseUser.email;
|
436 |
+
setTodayDate();
|
437 |
+
loadWorkouts(); // Charger les données en arrière plan
|
438 |
+
loadWorkoutTypes();
|
439 |
+
showPage('accueil-page'); // **MODIFIÉ** : Démarrer sur la page d'accueil
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
440 |
}
|
441 |
|
442 |
+
function handleAuthAction(event) { /* ... inchangé ... */ if (!auth || !authEmailInput || !authPasswordInput || !authActionButton) return; event.preventDefault(); const email = authEmailInput.value; const password = authPasswordInput.value; clearMessage(); if (!email || !password) { showMessage('Email et Mot de passe 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; authActionButton.textContent = 'S\'inscrire'; }); } }
|
443 |
+
function handlePasswordReset(event) { /* ... inchangé ... */ event.preventDefault(); if (!auth) { alert("Service d'authentification non disponible."); return; } clearMessage(); const email = prompt("Veuillez entrer votre adresse email pour recevoir le lien de réinitialisation :"); if (!email) { showMessage("Saisie de l'email annulée.", true); return; } console.log("Tentative d'envoi de l'email de réinitialisation à :", email); auth.sendPasswordResetEmail(email).then(() => { console.log("Email de réinitialisation envoyé."); showMessage("Si un compte existe pour " + email + ", un email de réinitialisation a été envoyé.", false); }).catch((error) => { console.error("Erreur sendPasswordResetEmail:", error); handleAuthError(error); }); }
|
444 |
+
function handleLogout() { /* ... inchangé ... */ if (!auth) return; console.log("Déconnexion..."); auth.signOut().catch(error => { console.error("Logout Error:", error); alert("Erreur lors de la déconnexion."); }); }
|
445 |
+
function authSwitchMode() { /* ... inchangé ... */ isLoginMode = !isLoginMode; clearMessage(); if (authEmailInput) authEmailInput.value = ''; if (authPasswordInput) authPasswordInput.value = ''; if (authTitle) authTitle.textContent = isLoginMode ? 'Connexion' : 'Inscription'; if (authActionButton) authActionButton.textContent = isLoginMode ? 'Se Connecter' : 'S\'inscrire'; if (authSwitchText) authSwitchText.textContent = isLoginMode ? 'Pas encore de compte ?' : 'Déjà un compte ?'; if (authSwitchLink) authSwitchLink.textContent = isLoginMode ? 'Inscrivez-vous ici' : 'Connectez-vous ici'; if (authEmailInput) authEmailInput.focus(); }
|
446 |
+
function handleAuthError(error) { /* ... inchangé ... */ console.error("Erreur Auth Firebase:", error.code, error.message); showMessage(getAuthErrorMessage(error), true); }
|
447 |
+
function getAuthErrorMessage(error) { /* ... inchangé ... */ switch (error.code) { case 'auth/invalid-email': return 'Format d\'email invalide.'; case 'auth/user-disabled': return 'Ce compte utilisateur a été désactivé.'; case 'auth/user-not-found': return 'Aucun utilisateur trouvé avec cet email.'; case 'auth/wrong-password': return 'Mot de passe incorrect.'; case 'auth/email-already-in-use': return 'Cet email est déjà utilisé par un autre compte.'; case 'auth/weak-password': return 'Le mot de passe doit contenir au moins 6 caractères.'; case 'auth/operation-not-allowed': return 'Méthode de connexion non activée.'; case 'auth/missing-password': return 'Mot de passe manquant.'; case 'auth/missing-email': return 'Adresse email manquante.'; default: console.error("Unhandled Auth Error Code:", error.code); return `Erreur (${error.code}). Veuillez réessayer.`; } }
|
448 |
+
function loadWorkoutTypes() { /* ... inchangé ... */ if (!currentFirebaseUser || !db || !typesSpinner) return; const userId = currentFirebaseUser.uid; console.log("Chargement types pour", userId); userWorkoutTypes = []; typesSpinner.classList.remove('hidden'); db.collection('workoutTypes').where('userId', '==', userId).orderBy('name').get().then(snapshot => { userWorkoutTypes = []; snapshot.forEach(doc => { userWorkoutTypes.push({ id: doc.id, ...doc.data() }); }); console.log("Types chargés:", userWorkoutTypes); renderWorkoutTypesList(); populateWorkoutTypeSelector(); }).catch(handleFirestoreError).finally(() => { typesSpinner.classList.add('hidden'); }); }
|
449 |
+
function renderWorkoutTypesList() { /* ... inchangé ... */ if (!workoutTypesList || !emptyTypesMessage) return; workoutTypesList.innerHTML = ''; if (userWorkoutTypes.length === 0) { emptyTypesMessage.classList.remove('hidden'); } else { emptyTypesMessage.classList.add('hidden'); userWorkoutTypes.forEach(type => { const li = document.createElement('li'); li.textContent = type.name; workoutTypesList.appendChild(li); }); } }
|
450 |
+
function handleAddWorkoutType(event) { /* ... inchangé ... */ event.preventDefault(); if (!currentFirebaseUser || !newTypeNameInput || !db || !addTypeError || !addTypeForm) return; const typeName = newTypeNameInput.value.trim(); addTypeError.textContent = ''; addTypeError.style.display = 'none'; if (!typeName) { addTypeError.textContent = "Le nom du type ne peut pas être vide."; addTypeError.style.display = 'block'; return; } if (userWorkoutTypes.some(t => t.name.toLowerCase() === typeName.toLowerCase())) { addTypeError.textContent = "Ce type de séance existe déjà."; addTypeError.style.display = 'block'; return; } console.log("Ajout type:", typeName); const typeData = { userId: currentFirebaseUser.uid, name: typeName, createdAt: firebase.firestore.FieldValue.serverTimestamp() }; const addBtn = addTypeForm.querySelector('button'); if (addBtn) { addBtn.disabled = true; addBtn.textContent = '...'; } db.collection('workoutTypes').add(typeData).then(() => { console.log("Type ajouté avec succès"); newTypeNameInput.value = ''; loadWorkoutTypes(); }).catch(handleFirestoreError).finally(() => { if (addBtn) { addBtn.disabled = false; addBtn.textContent = 'Ajouter'; } }); }
|
451 |
+
function populateWorkoutTypeSelector() { /* ... inchangé ... */ if (!selectWorkoutTypeDropdown) return; selectWorkoutTypeDropdown.innerHTML = '<option value="">-- Sélectionner Type --</option>'; userWorkoutTypes.forEach(type => { const option = document.createElement('option'); option.value = type.name; option.textContent = type.name; selectWorkoutTypeDropdown.appendChild(option); }); updateStartStructuredBtnState(); }
|
452 |
+
function updateStartStructuredBtnState() { /* ... inchangé ... */ if (!selectWorkoutTypeDropdown || !startStructuredWorkoutBtn) return; startStructuredWorkoutBtn.disabled = !selectWorkoutTypeDropdown.value; }
|
453 |
+
function loadWorkouts() { /* ... inchangé ... */ if (!currentFirebaseUser || !db || !spinner || !emptyWorkoutMessage || !workoutsList) { console.warn("Cannot load workouts: Missing user, db, or DOM elements."); if(workoutsList) workoutsList.innerHTML = ''; if(emptyWorkoutMessage) emptyWorkoutMessage.classList.remove('hidden'); workouts = []; updateStats(); return; } const userId = currentFirebaseUser.uid; console.log(`Chargement séances pour ${userId}...`); spinner.classList.remove('hidden'); emptyWorkoutMessage.classList.add('hidden'); workoutsList.innerHTML = ''; db.collection('workouts').where('userId', '==', userId).orderBy('date', 'desc').get().then((querySnapshot) => { workouts = []; console.log(`${querySnapshot.size} séances trouvées.`); querySnapshot.forEach((doc) => { const workoutData = doc.data(); workoutData.id = doc.id; workouts.push(workoutData); }); renderWorkoutsList(); updateStats(); }).catch(handleFirestoreError).finally(() => { spinner.classList.add('hidden'); if (workouts.length === 0) { emptyWorkoutMessage.classList.remove('hidden'); } }); }
|
454 |
+
function saveWorkout() { /* ... inchangé ... */ if (!currentFirebaseUser || !db || !exercisesContainer || !saveWorkoutBtn) { alert("Erreur: Impossible de sauvegarder."); 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 ? workoutDateInput.value : ''; const workoutDurationInput = document.getElementById('workout-duration'); const workoutDuration = workoutDurationInput ? parseInt(workoutDurationInput.value) || 0 : 0; const satisfaction = satisfactionRange ? parseInt(satisfactionRange.value) : 75; if (!selectedWorkoutTypeName) { alert("Erreur interne: Type de séance manquant."); showPage('seances-page'); return; } if (!workoutDate) { alert("Veuillez sélectionner une date."); if(workoutDateInput) workoutDateInput.focus(); return; } if (workoutDuration <= 0) { alert("Veuillez entrer une durée valide (minutes)."); if(workoutDurationInput) workoutDurationInput.focus(); return; } if (workoutExercisesForm.length === 0) { alert("Veuillez ajouter au moins un exercice."); return; } const exercises = []; let validationError = null; workoutExercisesForm.forEach((exerciseEl, index) => { if (validationError) return; const nameInput = exerciseEl.querySelector('.exercise-name'); const exerciseName = nameInput ? nameInput.value.trim() : ''; const unilateralCheckbox = exerciseEl.querySelector('.unilateral-checkbox'); const isUnilateral = unilateralCheckbox ? unilateralCheckbox.checked : false; 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 = `L'exercice "${exerciseName}" n'a pas de série.`; return; } seriesElements.forEach((seriesEl, sIndex) => { if (validationError) return; const repsInput = seriesEl.querySelector('.reps'); const weightInput = seriesEl.querySelector('.weight'); const degressiveCheckbox = seriesEl.querySelector('.degressive-checkbox'); const reps = repsInput ? parseInt(repsInput.value) || 0 : 0; const weight = weightInput ? parseFloat(weightInput.value) || 0 : 0; const isDegressive = degressiveCheckbox ? degressiveCheckbox.checked : false; if (reps <= 0) { validationError = `Reps invalides pour série ${sIndex + 1} (${exerciseName}).`; if(repsInput) repsInput.focus(); return; } if (weight < 0) { validationError = `Charge invalide pour série ${sIndex + 1} (${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 || 0) * (s.weight || 0) * (ex.isUnilateral ? 2 : 1); }); }); const finalWorkoutName = workoutName || selectedWorkoutTypeName; const workoutData = { userId: userId, workoutTypeName: selectedWorkoutTypeName, name: finalWorkoutName, date: workoutDate, duration: workoutDuration, exercises: exercises, totalTonnage: totalTonnage, satisfaction: satisfaction, lastUpdated: firebase.firestore.FieldValue.serverTimestamp() }; let savePromise; if (currentWorkoutId) { workoutData.createdAt = workouts.find(w => w.id === currentWorkoutId)?.createdAt || firebase.firestore.FieldValue.serverTimestamp(); savePromise = db.collection('workouts').doc(currentWorkoutId).set(workoutData); } else { workoutData.createdAt = firebase.firestore.FieldValue.serverTimestamp(); savePromise = db.collection('workouts').add(workoutData); } saveWorkoutBtn.disabled = true; saveWorkoutBtn.textContent = 'Sauvegarde...'; savePromise.then((docRefOrVoid) => { const savedId = currentWorkoutId || (docRefOrVoid ? docRefOrVoid.id : 'unknown'); console.log("Séance sauvegardée:", savedId); currentWorkoutId = null; try { if (typeof confetti === 'function') confetti({ particleCount: 150, spread: 90, origin: { y: 0.6 } }); } catch(e) { console.warn("Confetti error:", e)} showPage('seances-page'); loadWorkouts(); }).catch(handleFirestoreError).finally(() => { saveWorkoutBtn.disabled = false; saveWorkoutBtn.textContent = 'Enregistrer Séance'; }); }
|
455 |
+
function deleteWorkout() { /* ... inchangé ... */ if (!currentFirebaseUser || !currentWorkoutId || !db || !deleteWorkoutBtn) return; const workoutToDelete = workouts.find(w => w && w.id === currentWorkoutId); if (!workoutToDelete) { console.error("Workout to delete not found locally."); return; } const confirmMsg = `Supprimer la séance "${workoutToDelete.name || 'Sans nom'}" du ${new Date(workoutToDelete.date).toLocaleDateString('fr-FR')} ?`; if (!confirm(confirmMsg)) 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 avec succès:", currentWorkoutId); currentWorkoutId = null; showPage('seances-page'); loadWorkouts(); }).catch(handleFirestoreError).finally(() => { deleteWorkoutBtn.disabled = false; deleteWorkoutBtn.textContent = 'Supprimer Séance'; }); }
|
456 |
+
function setTodayDate() { /* ... inchangé ... */ if (workoutDateInput) { try { const today = new Date(); const offset = today.getTimezoneOffset(); const localDate = new Date(today.getTime() - (offset*60*1000)); workoutDateInput.value = localDate.toISOString().split('T')[0]; } catch (e) { console.error("Erreur setTodayDate:", e); try { workoutDateInput.value = new Date().toISOString().split('T')[0]; } catch {} } } }
|
457 |
+
|
458 |
+
function showPage(pageId) {
|
459 |
+
if (!appContainer) { console.error("App container not found!"); return; }
|
460 |
+
console.log(`Affichage page: ${pageId}`);
|
461 |
+
const pages = appContainer.querySelectorAll(':scope > div[id$="-page"]');
|
462 |
+
pages.forEach(page => page.classList.remove('active'));
|
463 |
+
const pageToShow = document.getElementById(pageId);
|
464 |
+
|
465 |
+
if (pageToShow) {
|
466 |
+
pageToShow.classList.add('active');
|
467 |
+
window.scrollTo(0, 0);
|
468 |
+
|
469 |
+
// Page-specific actions
|
470 |
+
if (pageId === 'new-workout-page') {
|
471 |
+
if (!selectedWorkoutTypeName) {
|
472 |
+
console.warn("Accès new-workout-page sans type.");
|
473 |
+
alert("Veuillez sélectionner un type.");
|
474 |
+
showPage('select-workout-type-page'); return;
|
475 |
+
}
|
476 |
+
renderActiveExerciseForm(); updateWorkoutTypeIndicator();
|
477 |
+
} else if (pageId === 'manage-types-page') loadWorkoutTypes();
|
478 |
+
else if (pageId === 'select-workout-type-page') populateWorkoutTypeSelector();
|
479 |
+
else if (pageId === 'stats-page') updateStats();
|
480 |
+
else if (pageId === 'workout-details-page' && !currentWorkoutId) {
|
481 |
+
console.warn("Accès détails sans ID.");
|
482 |
+
alert("Séance non spécifiée.");
|
483 |
+
showPage('seances-page'); return;
|
484 |
+
}
|
485 |
+
// No specific action needed for 'accueil-page' or 'seances-page' here
|
486 |
|
487 |
+
} else {
|
488 |
+
console.error(`Page ID "${pageId}" non trouvée. Affichage accueil.`);
|
489 |
+
if (accueilPage) accueilPage.classList.add('active'); // **MODIFIÉ** Fallback sur accueil
|
490 |
+
pageId = 'accueil-page'; // Update pageId for nav highlighting
|
491 |
}
|
492 |
|
493 |
+
// Update bottom navigation highlighting
|
494 |
+
navItems.forEach(item => item.classList.remove('active'));
|
495 |
+
// Trouve le nav item correspondant à la page actuelle (accueil, seances, ou stats)
|
496 |
+
const activeNavItem = document.querySelector(`.nav-item[data-page="${pageId}"]`);
|
497 |
+
if (activeNavItem) {
|
498 |
+
activeNavItem.classList.add('active');
|
499 |
+
} else {
|
500 |
+
// Si la page actuelle n'a pas de nav item direct (ex: détails), on active 'seances'
|
501 |
+
const seancesNavItem = document.querySelector('.nav-item[data-page="seances-page"]');
|
502 |
+
if(seancesNavItem) seancesNavItem.classList.add('active');
|
503 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
504 |
}
|
505 |
|
506 |
+
function handleAddNewExercise() { /* ... inchangé ... */ addExercise(true); }
|
507 |
+
function addExercise(navigateToNew = false) { /* ... inchangé ... */ if (!currentFirebaseUser || !exercisesContainer) { console.error("Cannot add exercise"); 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" required> <button class="btn btn-danger remove-exercise" style="padding: 0.3rem 0.6rem; flex-shrink: 0;" aria-label="Supprimer exercice">×</button> </div> <div class="form-group" style="margin: 0.5rem 0;"> <label style="display: inline-flex; align-items: center; color: var(--text-secondary); font-size: 0.9rem; font-weight: normal;"> <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: 1rem; 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.findIndex(el => el === exerciseDiv); if (indexToRemove > -1) { if (confirm("Supprimer cet exercice et toutes ses séries ?")) { exerciseDiv.remove(); workoutExercisesForm.splice(indexToRemove, 1); if (currentExerciseIndex >= indexToRemove && currentExerciseIndex > 0) { currentExerciseIndex--; } 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(); const nameInput = exerciseDiv.querySelector('.exercise-name'); if (nameInput) nameInput.focus(); } else { updateExerciseNavButtons(); } }
|
508 |
+
function addSeries(container) { /* ... inchangé ... */ if (!currentFirebaseUser || !container) { console.error("Cannot add series"); 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 style="font-weight: normal; font-size: 0.8rem;">Reps</label> <input type="number" class="reps" min="1" placeholder="10" style="padding: 0.5rem;" required> </div> <div class="form-group" style="flex: 1.2;"> <label style="font-weight: normal; font-size: 0.8rem;">Charge (kg)</label> <input type="number" class="weight" min="0" step="any" placeholder="20" style="padding: 0.5rem;" required> </div> <div class="form-group" style="flex: 1; display: flex; align-items: center; padding-bottom: 0.8rem; min-width: 100px;"> <label style="display: inline-flex; align-items: center; color: var(--text-secondary); font-size: 0.8rem; margin-bottom: 0; white-space: nowrap; font-weight: normal;"> <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.8rem; flex-basis: 30px; flex-grow: 0; align-self: center;" aria-label="Supprimer série">×</button> </div>`; container.appendChild(seriesDiv); const removeBtn = seriesDiv.querySelector('.remove-series'); removeBtn.addEventListener('click', () => { seriesDiv.remove(); }); }
|
509 |
+
function navigateExerciseForm(direction) { /* ... inchangé ... */ if (!exercisesContainer) return; workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); const newIndex = currentExerciseIndex + direction; if (newIndex >= 0 && newIndex < workoutExercisesForm.length) { currentExerciseIndex = newIndex; renderActiveExerciseForm(); } }
|
510 |
+
function renderActiveExerciseForm() { /* ... inchangé ... */ if (!exercisesContainer) return; workoutExercisesForm = Array.from(exercisesContainer.querySelectorAll('.exercise')); workoutExercisesForm.forEach((ex, index) => { if (index === currentExerciseIndex) { ex.classList.add('active-exercise'); ex.style.display = 'block'; } else { ex.classList.remove('active-exercise'); ex.style.display = 'none'; } }); updateExerciseNavButtons(); }
|
511 |
+
function updateExerciseNavButtons() { /* ... inchangé ... */ if (!currentExerciseIndicator || !prevExerciseBtn || !nextExerciseBtn) { console.warn("Exercise nav elements missing"); return; } 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); } }
|
512 |
+
function clearNewWorkoutForm(typeName = "Libre") { /* ... inchangé ... */ console.log("Nettoyage formulaire pour type:", typeName); if (!currentFirebaseUser) { console.error("Cannot clear form: No user"); return; } selectedWorkoutTypeName = typeName; const workoutNameInput = document.getElementById('workout-name'); if (workoutNameInput) workoutNameInput.value = (typeName === "Libre" ? "" : typeName); if (workoutDateInput) setTodayDate(); const durationInput = document.getElementById('workout-duration'); if (durationInput) durationInput.value = '60'; if (exercisesContainer) exercisesContainer.innerHTML = ''; workoutExercisesForm = []; if (satisfactionRange) satisfactionRange.value = 75; if (satisfactionValue) satisfactionValue.textContent = '75%'; currentWorkoutId = null; currentExerciseIndex = 0; addExercise(false); updateWorkoutTypeIndicator(); renderActiveExerciseForm(); }
|
513 |
+
function updateWorkoutTypeIndicator() { /* ... inchangé ... */ if (!workoutTypeIndicator) return; if (selectedWorkoutTypeName && selectedWorkoutTypeName !== "Libre") { workoutTypeIndicator.textContent = selectedWorkoutTypeName; workoutTypeIndicator.classList.remove('hidden'); } else { workoutTypeIndicator.classList.add('hidden'); } }
|
514 |
+
function renderWorkoutsList() { /* ... inchangé ... */ if (!workoutsList || !emptyWorkoutMessage) { console.warn("Cannot render workout list: DOM elements missing."); return; } workoutsList.innerHTML = ''; if (!workouts || 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 => { if (!workout || !workout.id) { console.warn("Skipping invalid workout data:", workout); return; } const workoutDate = workout.date ? new Date(workout.date).toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric' }) : 'Date inconnue'; 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" style="margin-left: 8px;">${workout.workoutTypeName}</span>` : ''; const exercisesCount = (workout.exercises || []).length; workoutDiv.innerHTML = `<div class="workout-header"><h3 style="margin-bottom: 0.5rem;">${workout.name || 'Séance sans nom'} ${typeBadge}</h3><div class="badge">${workoutDate}</div></div><div class="workout-details"><span>${workout.duration || '?'} min</span> | <span>${exercisesCount} exo${exercisesCount > 1 ? 's' : ''}</span> | <span>${(workout.totalTonnage || 0).toFixed(1)} kg</span> | <span>${workout.satisfaction || '?'}%</span></div>`; workoutDiv.addEventListener('click', () => displayWorkoutDetails(workout.id)); workoutsList.appendChild(workoutDiv); }); } }
|
515 |
+
function displayWorkoutDetails(workoutId) { /* ... inchangé ... */ if (!currentFirebaseUser) { console.error("User not logged in for details"); return; } const workout = workouts.find(w => w && w.id === workoutId); if (!workout) { alert("Détails de la séance introuvables."); showPage('seances-page'); return; } console.log("Affichage détails pour:", workoutId, workout); const detailNameEl = document.getElementById('detail-workout-name'); const detailDateEl = document.getElementById('detail-date'); const detailDurationEl = document.getElementById('detail-duration'); const detailTonnageEl = document.getElementById('detail-tonnage'); const detailSatisfactionEl = document.getElementById('detail-satisfaction'); const detailExercisesCountEl = document.getElementById('detail-exercises-count'); const detailExercisesContainer = document.getElementById('detail-exercises-container'); const detailWorkoutTypeBadge = document.getElementById('detail-workout-type'); if (detailWorkoutTypeBadge) { if (workout.workoutTypeName && workout.workoutTypeName !== "Libre") { detailWorkoutTypeBadge.textContent = workout.workoutTypeName; detailWorkoutTypeBadge.classList.remove('hidden'); } else { detailWorkoutTypeBadge.classList.add('hidden'); } } if (detailNameEl) detailNameEl.textContent = workout.name || 'Séance sans nom'; if (detailDateEl) detailDateEl.textContent = workout.date ? new Date(workout.date).toLocaleDateString('fr-FR') : 'Inconnue'; if (detailDurationEl) detailDurationEl.textContent = workout.duration || '?'; if (detailTonnageEl) detailTonnageEl.textContent = (workout.totalTonnage || 0).toFixed(1); if (detailSatisfactionEl) detailSatisfactionEl.textContent = `${workout.satisfaction || '?'}%`; const exercisesCount = (workout.exercises || []).length; if (detailExercisesCountEl) detailExercisesCountEl.textContent = exercisesCount; if (detailExercisesContainer) { detailExercisesContainer.innerHTML = ''; if (exercisesCount > 0) { workout.exercises.forEach((exercise) => { if (!exercise) return; 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(--info); color: var(--text-light);">Dégr.</span>' : ''; const weightFactor = exercise.isUnilateral ? 2 : 1; const seriesTonnage = (serie.reps || 0) * (serie.weight || 0) * 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: var(--text-secondary);">(${seriesTonnage.toFixed(1)} kg)</span></div></div>`; }); const unilateralLabel = exercise.isUnilateral ? ' <span class="badge" style="font-size: 0.7rem; background-color: var(--info); color: var(--text-light);">Unilat.</span>' : ''; exerciseDiv.innerHTML = `<h4 style="font-size: 1.1rem; margin-bottom: 0.8rem; color: var(--text-light); text-transform: none; font-family: var(--font-primary); letter-spacing: normal;">${exercise.name || 'Exercice sans nom'}${unilateralLabel}</h4> <div class="series-summary-container"> ${seriesHtml} </div>`; detailExercisesContainer.appendChild(exerciseDiv); }); } else { detailExercisesContainer.innerHTML = '<p style="color: var(--text-secondary); text-align: center;">Aucun exercice enregistré.</p>'; } } currentWorkoutId = workoutId; showPage('workout-details-page'); }
|
516 |
+
function updateStats() { /* ... inchangé ... */ console.log("MàJ stats..."); if (!currentFirebaseUser || !statsWorkoutCountEl || !statsAvgTonnageEl || !statsAvgSatisfactionEl || !typeTrendsContainer || !noTrendsMessage) { console.warn("Cannot update stats: Missing elements."); if(statsWorkoutCountEl) statsWorkoutCountEl.textContent = '0'; if(statsAvgTonnageEl) statsAvgTonnageEl.textContent = '0'; if(statsAvgSatisfactionEl) statsAvgSatisfactionEl.textContent = '0%'; if(typeTrendsContainer) typeTrendsContainer.innerHTML = ''; if(noTrendsMessage) noTrendsMessage.classList.remove('hidden'); return; } const workoutCount = workouts.length; statsWorkoutCountEl.textContent = workoutCount; if (workoutCount === 0) { statsAvgTonnageEl.textContent = '0'; statsAvgSatisfactionEl.textContent = '0%'; renderTypeTrends({}); return; } const totalTonnageAll = workouts.reduce((sum, w) => sum + (w.totalTonnage || 0), 0); statsAvgTonnageEl.textContent = workoutCount > 0 ? (totalTonnageAll / workoutCount).toFixed(1) : '0'; const totalSatisfaction = workouts.reduce((sum, w) => sum + (w.satisfaction || 0), 0); statsAvgSatisfactionEl.textContent = workoutCount > 0 ? `${Math.round(totalSatisfaction / workoutCount)}%` : '0%'; console.log("Calcul tendances..."); const trendsData = {}; const structuredWorkouts = workouts.filter(w => w && w.workoutTypeName && w.workoutTypeName !== "Libre"); const groupedByType = structuredWorkouts.reduce((acc, workout) => { const type = workout.workoutTypeName; if (!acc[type]) acc[type] = []; acc[type].push(workout); return acc; }, {}); for (const typeName in groupedByType) { const sessionsOfType = groupedByType[typeName].sort((a, b) => new Date(a.date) - new Date(b.date)); trendsData[typeName] = sessionsOfType.slice(-3).map(s => ({ date: s.date, tonnage: s.totalTonnage || 0 })); } console.log("Données tendances:", trendsData); renderTypeTrends(trendsData); }
|
517 |
+
function renderTypeTrends(trends) { /* ... inchangé ... */ if (!typeTrendsContainer || !noTrendsMessage || !trendsSpinner) { console.error("Cannot render trends: Missing DOM elements."); return;} trendsSpinner.classList.add('hidden'); typeTrendsContainer.innerHTML = ''; const trendTypes = Object.keys(trends); if (trendTypes.length === 0) { noTrendsMessage.classList.remove('hidden'); } else { noTrendsMessage.classList.add('hidden'); trendTypes.sort().forEach(typeName => { const typeData = trends[typeName]; if (!typeData || typeData.length === 0) return; const card = document.createElement('div'); card.className = 'card type-trend-card'; let listItems = ''; typeData.forEach(session => { const formattedDate = session.date ? new Date(session.date).toLocaleDateString('fr-FR', { day: '2-digit', month: 'short' }) : '??'; const tonnageText = (session.tonnage !== undefined && session.tonnage !== null) ? `${session.tonnage.toFixed(1)} kg` : 'N/A'; listItems += `<li><span>${formattedDate}</span> <strong>${tonnageText}</strong></li>`; }); card.innerHTML = `<h4>${typeName} (3 Dernières)</h4><ul>${listItems}</ul>`; typeTrendsContainer.appendChild(card); }); } }
|
518 |
+
function handleFirestoreError(error) { /* ... inchangé ... */ console.error("Erreur Firestore:", error.code, error.message); alert(`Erreur de base de données: ${error.message}`); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
519 |
|
520 |
// --- ÉCOUTEURS D'ÉVÉNEMENTS ---
|
521 |
function initEventListeners() {
|
522 |
console.log("initEventListeners: Attachement...");
|
523 |
+
// Logo
|
524 |
+
if (logoLink) logoLink.addEventListener('click', (e) => { e.preventDefault(); if (currentFirebaseUser) showPage('accueil-page'); }); else console.error("!logoLink");
|
525 |
// Auth
|
526 |
if (loginForm) loginForm.addEventListener('submit', handleAuthAction); else console.error("!loginForm");
|
527 |
if (authSwitchLink) authSwitchLink.addEventListener('click', authSwitchMode); else console.error("!authSwitchLink");
|
528 |
+
if (forgotPasswordLink) forgotPasswordLink.addEventListener('click', handlePasswordReset); else console.error("!forgotPasswordLink");
|
529 |
if (logoutBtn) logoutBtn.addEventListener('click', handleLogout); else console.error("!logoutBtn");
|
530 |
// Nav
|
531 |
navItems.forEach((item) => { if (item) item.addEventListener('click', (e) => { e.preventDefault(); const targetPageId = item.getAttribute('data-page'); if (!currentFirebaseUser && targetPageId !== 'login-page') { showLoginPage(); return; } if (targetPageId) showPage(targetPageId); }); else console.error(`!navItem`); });
|
|
|
538 |
if (selectWorkoutTypeDropdown) selectWorkoutTypeDropdown.addEventListener('change', updateStartStructuredBtnState); else console.error("!selectWorkoutTypeDropdown");
|
539 |
if (startStructuredWorkoutBtn) startStructuredWorkoutBtn.addEventListener('click', () => { if (selectWorkoutTypeDropdown) { const type = selectWorkoutTypeDropdown.value; if (type) { clearNewWorkoutForm(type); showPage('new-workout-page'); } else { alert("Veuillez sélectionner un type de séance."); } } }); else console.error("!startStructuredWorkoutBtn");
|
540 |
if (startFreeWorkoutBtn) startFreeWorkoutBtn.addEventListener('click', () => { clearNewWorkoutForm("Libre"); showPage('new-workout-page'); }); else console.error("!startFreeWorkoutBtn");
|
541 |
+
// General Nav (Retour) -> Va maintenant à seances-page
|
542 |
+
backToSeancesBtns.forEach(btn => { if (btn) btn.addEventListener('click', () => showPage('seances-page')); else console.error("!backToSeancesBtn"); });
|
543 |
// New Workout Actions
|
544 |
+
if (cancelNewWorkoutBtn) cancelNewWorkoutBtn.addEventListener('click', () => { if (confirm("Annuler cette séance ? Les données non enregistrées seront perdues.")) showPage('seances-page'); }); // **MODIFIÉ** Retour vers les séances
|
545 |
+
else console.error("!cancelNewWorkoutBtn");
|
546 |
if (saveWorkoutBtn) saveWorkoutBtn.addEventListener('click', saveWorkout); else console.error("!saveWorkoutBtn");
|
547 |
if (addExerciseBtn) addExerciseBtn.addEventListener('click', handleAddNewExercise); else console.error("!addExerciseBtn");
|
548 |
// Exercise Nav
|
|
|
561 |
|
562 |
// --- INITIALISATION ---
|
563 |
document.addEventListener('DOMContentLoaded', () => {
|
564 |
+
console.log("DOM chargé. Initialisation...");
|
565 |
+
if (typeof firebase !== 'undefined' && auth && db) {
|
566 |
+
console.log("Firebase prêt. Initialisation listeners...");
|
567 |
+
initEventListeners();
|
568 |
+
initAuthListener(); // Gère l'état initial
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
569 |
} else {
|
570 |
+
console.error("ERREUR CRITIQUE: Firebase non initialisé ou SDK manquant.");
|
571 |
+
document.body.innerHTML = '<div style="color: red; padding: 30px; text-align: center; font-family: sans-serif; background-color: #333;"><h1>Erreur Critique</h1><p>Impossible de charger les services nécessaires. Vérifiez la console (F12).</p></div>';
|
|
|
572 |
}
|
573 |
}); // FIN DOMContentLoaded
|
574 |
|