Spaces:
Running
Running
Update index.html
Browse files- index.html +202 -349
index.html
CHANGED
@@ -31,6 +31,7 @@
|
|
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 |
--border-color: #333333; /* Couleur des bordures */
|
35 |
|
36 |
--font-primary: 'Roboto', sans-serif; /* Police par défaut */
|
@@ -38,312 +39,89 @@
|
|
38 |
}
|
39 |
/* ===== FIN NOUVELLES VARIABLES ===== */
|
40 |
|
41 |
-
* {
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
}
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
}
|
55 |
-
|
56 |
-
.
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
}
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
flex-direction: column; /* Logo au-dessus du slogan */
|
71 |
-
align-items: center; /* Centrage horizontal */
|
72 |
-
}
|
73 |
-
|
74 |
-
header img#app-logo {
|
75 |
-
max-width: 250px; /* Ajustez la taille selon vos besoins */
|
76 |
-
height: auto;
|
77 |
-
margin-bottom: 0.5rem; /* Espace entre logo et slogan */
|
78 |
-
}
|
79 |
-
|
80 |
-
header p {
|
81 |
-
color: var(--text-secondary);
|
82 |
-
font-size: 0.9rem;
|
83 |
-
}
|
84 |
-
/* Fin modif header */
|
85 |
-
|
86 |
-
h1, h2, h3, h4 {
|
87 |
-
/* Application de la police titre + couleur accent */
|
88 |
-
font-family: var(--font-headings);
|
89 |
-
font-weight: 700; /* Assurez-vous que le poids est chargé */
|
90 |
-
color: var(--accent);
|
91 |
-
text-transform: uppercase; /* Style COCKTAIL */
|
92 |
-
letter-spacing: 0.5px; /* Léger espacement */
|
93 |
-
}
|
94 |
-
|
95 |
-
.btn {
|
96 |
-
background-color: var(--accent);
|
97 |
-
/* Texte sur bouton doit contraster avec le jaune */
|
98 |
-
color: var(--text-on-accent);
|
99 |
-
border: none;
|
100 |
-
padding: 0.6rem 1.2rem;
|
101 |
-
border-radius: 4px;
|
102 |
-
cursor: pointer;
|
103 |
-
font-weight: bold; /* Utilise le poids de Roboto Bold par défaut */
|
104 |
-
font-family: var(--font-headings); /* Police titre sur boutons */
|
105 |
-
text-transform: uppercase; /* Style COCKTAIL */
|
106 |
-
transition: background-color 0.2s, color 0.2s;
|
107 |
-
}
|
108 |
-
|
109 |
-
.btn:hover {
|
110 |
-
background-color: var(--accent-dark);
|
111 |
-
color: var(--text-on-accent);
|
112 |
-
}
|
113 |
-
|
114 |
-
.btn:disabled {
|
115 |
-
background-color: #555;
|
116 |
-
color: #999;
|
117 |
-
cursor: not-allowed;
|
118 |
-
}
|
119 |
-
|
120 |
-
.btn-outline {
|
121 |
-
background-color: transparent;
|
122 |
-
color: var(--accent);
|
123 |
-
border: 1px solid var(--accent);
|
124 |
-
}
|
125 |
-
.btn-outline:hover {
|
126 |
-
background-color: rgba(255, 193, 7, 0.1); /* Léger fond jaune au survol */
|
127 |
-
color: var(--accent);
|
128 |
-
}
|
129 |
-
|
130 |
-
.btn-outline:disabled {
|
131 |
-
color: #555;
|
132 |
-
border-color: #555;
|
133 |
-
background-color: transparent;
|
134 |
-
}
|
135 |
-
|
136 |
-
.btn-danger {
|
137 |
-
background-color: var(--danger);
|
138 |
-
color: white; /* Texte blanc sur rouge */
|
139 |
-
border-color: var(--danger);
|
140 |
-
}
|
141 |
-
.btn-danger:hover {
|
142 |
-
background-color: #d32f2f; /* Rouge plus foncé */
|
143 |
-
color: white;
|
144 |
-
}
|
145 |
-
|
146 |
-
.btn-info {
|
147 |
-
background-color: var(--info);
|
148 |
-
color: var(--text-light); /* Texte clair sur fond gris */
|
149 |
-
border-color: var(--info);
|
150 |
-
}
|
151 |
-
.btn-info:hover {
|
152 |
-
background-color: #5a6268; /* Gris plus foncé */
|
153 |
-
color: var(--text-light);
|
154 |
-
}
|
155 |
-
|
156 |
-
input, select, textarea {
|
157 |
-
width: 100%;
|
158 |
-
padding: 0.7rem; /* Légèrement plus de padding */
|
159 |
-
margin-bottom: 1rem;
|
160 |
-
background-color: #2a2a2a;
|
161 |
-
border: 1px solid #444;
|
162 |
-
border-radius: 4px;
|
163 |
-
color: var(--text-light);
|
164 |
-
font-size: 1rem; /* Taille de police standard */
|
165 |
-
}
|
166 |
-
input:focus, select:focus, textarea:focus {
|
167 |
-
outline: none;
|
168 |
-
border-color: var(--accent);
|
169 |
-
box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.3); /* Ombre focus jaune */
|
170 |
-
}
|
171 |
-
|
172 |
-
input[type="checkbox"] {
|
173 |
-
width: auto;
|
174 |
-
margin-right: 0.5rem;
|
175 |
-
vertical-align: middle;
|
176 |
-
accent-color: var(--accent); /* Couleur de la coche */
|
177 |
-
}
|
178 |
-
|
179 |
-
.card {
|
180 |
-
background-color: var(--bg-card);
|
181 |
-
border-radius: 8px;
|
182 |
-
padding: 1.2rem; /* Padding un peu plus grand */
|
183 |
-
margin-bottom: 1rem;
|
184 |
-
box-shadow: 0 4px 8px rgba(0,0,0,0.3); /* Ombre un peu plus prononcée */
|
185 |
-
}
|
186 |
-
|
187 |
.form-group { margin-bottom: 1rem; }
|
188 |
-
.form-row { display: flex; gap: 1rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
|
189 |
.form-row > * { flex: 1; margin-bottom: 0; min-width: 100px; }
|
190 |
|
191 |
-
|
192 |
-
|
193 |
-
margin-bottom: 0.4rem; /* Espace label/input */
|
194 |
-
color: var(--text-secondary);
|
195 |
-
font-size: 0.9rem;
|
196 |
-
font-weight: bold; /* Labels en gras */
|
197 |
-
}
|
198 |
|
199 |
-
/* Exercise
|
200 |
-
.exercise {
|
201 |
-
border-left: 4px solid var(--accent); /* Bordure accentuée */
|
202 |
-
padding-left: 1rem;
|
203 |
-
margin-bottom: 1.5rem;
|
204 |
-
display: none;
|
205 |
-
background-color: rgba(0,0,0,0.1); /* Léger fond pour démarquer */
|
206 |
-
padding: 1rem; /* Padding interne */
|
207 |
-
border-radius: 0 4px 4px 0;
|
208 |
-
}
|
209 |
.exercise.active-exercise { display: block; animation: fadeIn 0.3s ease-in-out; }
|
210 |
.exercise-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; gap: 0.5rem;}
|
211 |
.exercise-header input { margin-bottom: 0; }
|
212 |
-
.series-container { margin-left: 0rem; margin-top: 1rem; }
|
213 |
-
.series { background-color: #282828; padding: 0.8rem; border-radius: 4px; margin-bottom: 0.8rem; border: 1px solid #333; }
|
214 |
|
215 |
/* Nav Bottom */
|
216 |
-
.nav-bottom {
|
217 |
-
|
218 |
-
background-color: #1a1a1a;
|
219 |
-
display: flex; justify-content: space-around;
|
220 |
-
padding: 0.7rem 0;
|
221 |
-
box-shadow: 0 -3px 10px rgba(0,0,0,0.4);
|
222 |
-
z-index: 10;
|
223 |
-
}
|
224 |
-
.nav-item {
|
225 |
-
text-align: center; color: var(--text-secondary);
|
226 |
-
text-decoration: none; font-size: 0.85rem;
|
227 |
-
transition: color 0.2s; padding: 0 0.5rem;
|
228 |
-
}
|
229 |
.nav-item.active { color: var(--accent); font-weight: bold; }
|
230 |
-
.nav-icon { font-size: 1.5rem; margin-bottom: 0.2rem; }
|
231 |
-
|
232 |
-
/*
|
233 |
-
.workout-card {
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
}
|
238 |
-
.workout-
|
239 |
-
|
240 |
-
|
241 |
-
}
|
242 |
-
.workout-header {
|
243 |
-
display: flex;
|
244 |
-
align-items: center; /* Centre verticalement titre et badge */
|
245 |
-
gap: 0.75rem; /* Espace entre le titre et le badge */
|
246 |
-
margin-bottom: 0.5rem; /* Espace sous l'en-tête, avant les détails */
|
247 |
-
}
|
248 |
-
.workout-header h3 {
|
249 |
-
color: var(--text-light);
|
250 |
-
font-family: var(--font-primary);
|
251 |
-
text-transform: none;
|
252 |
-
letter-spacing: normal;
|
253 |
-
font-weight: bold;
|
254 |
-
flex-grow: 1; /* Permet au titre de prendre l'espace disponible */
|
255 |
-
min-width: 0; /* Important pour permettre au titre de rétrécir */
|
256 |
-
position: static; /* Assure positionnement normal */
|
257 |
-
margin-bottom: 0; /* Retiré car géré par gap */
|
258 |
-
}
|
259 |
-
/* Cible spécifiquement le badge DANS le .workout-header (le badge de date) */
|
260 |
-
.workout-header > .badge {
|
261 |
-
background-color: #2a2a2a; /* Fond plus discret, correspond aux inputs */
|
262 |
-
color: var(--text-secondary); /* Texte gris clair */
|
263 |
-
padding: 0.3rem 0.6rem;
|
264 |
-
border-radius: 4px; /* Moins arrondi, plus sobre */
|
265 |
-
font-size: 0.8rem;
|
266 |
-
font-weight: normal; /* Date pas forcément en gras */
|
267 |
-
white-space: nowrap; /* Garde la date sur une ligne */
|
268 |
-
flex-shrink: 0; /* Empêche le badge de rétrécir */
|
269 |
-
margin-left: auto; /* Pousse le badge vers la droite après que le h3 ait pris sa place */
|
270 |
-
position: static; /* Assure positionnement normal */
|
271 |
-
}
|
272 |
-
.workout-details {
|
273 |
-
font-size: 0.9rem;
|
274 |
-
color: var(--text-secondary);
|
275 |
-
margin-top: 0.8rem; /* Rétabli ou ajusté */
|
276 |
-
}
|
277 |
-
.workout-details span {
|
278 |
-
margin-right: 0.8rem; /* Espacement entre les détails */
|
279 |
-
}
|
280 |
-
/* Assure que le badge de type à l'intérieur du h3 garde son style */
|
281 |
-
.workout-header h3 .badge.type-badge {
|
282 |
-
background-color: var(--neutral-white);
|
283 |
-
color: var(--bg-dark);
|
284 |
-
padding: 0.3rem 0.6rem;
|
285 |
-
border-radius: 12px;
|
286 |
-
font-size: 0.8rem;
|
287 |
-
font-weight: bold;
|
288 |
-
white-space: nowrap;
|
289 |
-
margin-left: 5px;
|
290 |
-
/* On s'assure qu'il n'hérite pas du style du badge de date */
|
291 |
-
position: static;
|
292 |
-
flex-shrink: initial;
|
293 |
-
margin-left: 5px; /* Garde la marge gauche du badge type */
|
294 |
-
}
|
295 |
-
/* ===== FIN CSS MODIFIÉ POUR LA CARTE SÉANCE ===== */
|
296 |
-
|
297 |
|
298 |
/* Stats */
|
299 |
.stat-card { text-align: center; padding: 1rem; }
|
300 |
-
.stat-value {
|
301 |
-
|
302 |
-
|
303 |
-
font-weight: bold;
|
304 |
-
font-family: var(--font-headings); /* Police titre */
|
305 |
-
margin-bottom: 0.3rem;
|
306 |
-
}
|
307 |
-
.stat-card div:last-child { color: var(--text-secondary); font-size: 0.9rem; } /* Label stat */
|
308 |
-
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1rem; } /* Min width augmentée */
|
309 |
|
310 |
-
|
|
|
311 |
#app-container > div:not(.active) { display: none !important; }
|
312 |
-
.flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; }
|
313 |
-
|
314 |
-
/* Badges Généraux (non date, non type) */
|
315 |
-
.badge {
|
316 |
-
background-color: var(--accent);
|
317 |
-
color: var(--text-on-accent);
|
318 |
-
padding: 0.3rem 0.6rem; /* Padding ajusté */
|
319 |
-
border-radius: 12px; /* Plus arrondi */
|
320 |
-
font-size: 0.8rem;
|
321 |
-
font-weight: bold;
|
322 |
-
white-space: nowrap;
|
323 |
-
margin-left: 5px;
|
324 |
-
}
|
325 |
-
/* Style spécifique pour le badge de type déjà géré plus haut */
|
326 |
|
|
|
|
|
|
|
327 |
|
328 |
/* Spinner */
|
329 |
-
.spinner {
|
330 |
-
border: 4px solid rgba(255, 255, 255, 0.1);
|
331 |
-
width: 36px; height: 36px;
|
332 |
-
border-radius: 50%;
|
333 |
-
border-left-color: var(--accent); /* Couleur accent pour spinner */
|
334 |
-
animation: spin 1s linear infinite;
|
335 |
-
margin: 2rem auto; display: none;
|
336 |
-
}
|
337 |
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
338 |
|
339 |
/* Exercise Summary in Details */
|
340 |
-
.exercise-summary {
|
341 |
-
margin: 0.3rem 0; padding: 0.5rem 0;
|
342 |
-
border-bottom: 1px solid var(--border-color);
|
343 |
-
font-size: 0.9rem;
|
344 |
-
}
|
345 |
.exercise-summary:last-child { border-bottom: none; }
|
346 |
-
.series-summary-container .badge { font-size: 0.7rem; padding: 0.2rem 0.4rem; background-color: var(--info); color: var(--text-light); }
|
347 |
|
348 |
/* Satisfaction Slider */
|
349 |
.satisfaction { display: flex; align-items: center; justify-content: center; flex-direction: column; margin-top: 1rem; }
|
@@ -357,10 +135,16 @@
|
|
357 |
/* Login Page */
|
358 |
#login-page .card { max-width: 400px; margin: 3rem auto; }
|
359 |
#login-page h2 { text-align: center; margin-bottom: 1.5rem; color: var(--accent); }
|
360 |
-
|
|
|
|
|
|
|
361 |
#login-form button { margin-top: 1rem; }
|
362 |
-
.auth-
|
363 |
-
.auth-switch
|
|
|
|
|
|
|
364 |
|
365 |
/* User Info */
|
366 |
.user-info { text-align: right; margin-bottom: 1rem; font-size: 0.9rem; color: var(--text-secondary);}
|
@@ -368,13 +152,7 @@
|
|
368 |
.user-info button { margin-left: 0.8rem; padding: 0.3rem 0.7rem; font-size: 0.8rem; }
|
369 |
|
370 |
/* Manage Types Page */
|
371 |
-
#workout-types-list li {
|
372 |
-
display: flex; justify-content: space-between; align-items: center;
|
373 |
-
background-color: #2a2a2a; padding: 0.7rem 1rem; /* Padding augmenté */
|
374 |
-
margin-bottom: 0.5rem; border-radius: 4px;
|
375 |
-
border-left: 4px solid var(--neutral-white); /* Bordure blanche */
|
376 |
-
color: var(--text-light); font-weight: bold;
|
377 |
-
}
|
378 |
#add-type-form { display: flex; gap: 0.5rem; align-items: flex-end;}
|
379 |
#add-type-form input { margin-bottom: 0; flex-grow: 1;}
|
380 |
#add-type-form button { flex-shrink: 0; }
|
@@ -382,23 +160,17 @@
|
|
382 |
|
383 |
/* Select Workout Type Page */
|
384 |
#select-workout-type-page .card { text-align: center; }
|
385 |
-
#select-workout-type-page .btn { margin-top: 1rem; width: 80%; }
|
386 |
#select-workout-type { margin-bottom: 1rem;}
|
387 |
|
388 |
/* Type Trend Card */
|
389 |
.type-trend-card { margin-bottom: 1.5rem; }
|
390 |
-
.type-trend-card h4 {
|
391 |
-
/* Style titre appliqué par défaut */
|
392 |
-
color: var(--neutral-white); /* Titre Tendance en Blanc */
|
393 |
-
border-bottom: 1px solid var(--border-color);
|
394 |
-
padding-bottom: 0.5rem; margin-bottom: 1rem;
|
395 |
-
text-transform: uppercase; /* Garde uppercase pour section */
|
396 |
-
}
|
397 |
.type-trend-card ul { list-style: none; padding: 0; font-size: 0.95rem; }
|
398 |
.type-trend-card li { margin-bottom: 0.6rem; color: var(--text-light); display: flex; justify-content: space-between; border-bottom: 1px dashed #333; padding-bottom: 0.4rem;}
|
399 |
.type-trend-card li:last-child { border-bottom: none; }
|
400 |
-
.type-trend-card li span:first-child { color: var(--text-secondary); font-size: 0.85rem; margin-right: 1rem; }
|
401 |
-
.type-trend-card li strong { color: var(--accent); }
|
402 |
|
403 |
|
404 |
/* Media Queries */
|
@@ -408,11 +180,9 @@
|
|
408 |
.form-row { flex-direction: column; gap: 0.8rem; }
|
409 |
.flex-between { flex-direction: column; align-items: stretch; gap: 0.8rem; }
|
410 |
.flex-between > div { width: 100%; display: flex; justify-content: center; margin-top: 0.5rem;}
|
411 |
-
|
412 |
-
.workout-header {
|
413 |
-
.workout-header
|
414 |
-
.workout-header > .badge { margin-left: 0; /* Pas besoin de le pousser à droite si ça passe en dessous */ }
|
415 |
-
|
416 |
.user-info { text-align: center; margin-bottom: 0.8rem;}
|
417 |
.user-info button { display: block; margin: 0.5rem auto 0;}
|
418 |
.exercise-navigation button { padding: 0.6rem; font-size: 0.9rem;}
|
@@ -426,9 +196,8 @@
|
|
426 |
</head>
|
427 |
<body>
|
428 |
<div class="container">
|
429 |
-
<!-- Header
|
430 |
<header>
|
431 |
-
<!-- Assurez-vous que le fichier 'input_file_0.png' est bien à la racine -->
|
432 |
<img src="input_file_0.png" alt="Cocktail Fitness Logo" id="app-logo">
|
433 |
<p>Votre suivi de séances personnalisé</p>
|
434 |
</header>
|
@@ -437,13 +206,22 @@
|
|
437 |
<div id="login-page">
|
438 |
<div class="card">
|
439 |
<h2 id="auth-title">Connexion</h2>
|
440 |
-
<form id="login-form" novalidate>
|
441 |
<div class="form-group"> <label for="auth-email">Email</label> <input type="email" id="auth-email" placeholder="[email protected]" required> </div>
|
442 |
<div class="form-group"> <label for="auth-password">Mot de passe</label> <input type="password" id="auth-password" placeholder="********" required> </div>
|
443 |
-
|
|
|
444 |
<button type="submit" id="auth-action-btn" class="btn" style="width: 100%;">Se Connecter</button>
|
445 |
</form>
|
446 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
447 |
</div>
|
448 |
</div>
|
449 |
|
@@ -455,6 +233,7 @@
|
|
455 |
</div>
|
456 |
|
457 |
<div id="app-container">
|
|
|
458 |
<!-- Home Page -->
|
459 |
<div id="home-page">
|
460 |
<div class="flex-between">
|
@@ -601,7 +380,7 @@
|
|
601 |
<!-- ================= JAVASCRIPT =================== -->
|
602 |
<!-- ================================================== -->
|
603 |
<script>
|
604 |
-
// Firebase Init
|
605 |
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" };
|
606 |
let app, auth, db;
|
607 |
try {
|
@@ -616,14 +395,7 @@
|
|
616 |
}
|
617 |
|
618 |
// --- Variables d'État ---
|
619 |
-
let workouts = [];
|
620 |
-
let currentWorkoutId = null;
|
621 |
-
let currentExerciseIndex = 0;
|
622 |
-
let workoutExercisesForm = [];
|
623 |
-
let currentFirebaseUser = null;
|
624 |
-
let userWorkoutTypes = [];
|
625 |
-
let selectedWorkoutTypeName = null;
|
626 |
-
let isLoginMode = true;
|
627 |
|
628 |
// --- Éléments DOM ---
|
629 |
const loginPage = document.getElementById('login-page');
|
@@ -633,9 +405,10 @@
|
|
633 |
const loginForm = document.getElementById('login-form');
|
634 |
const authActionButton = document.getElementById('auth-action-btn');
|
635 |
const authSwitchLink = document.getElementById('auth-switch-link');
|
|
|
636 |
const authTitle = document.getElementById('auth-title');
|
637 |
const authSwitchText = document.getElementById('auth-switch-text');
|
638 |
-
const
|
639 |
const currentUserDisplay = document.getElementById('current-user-display');
|
640 |
const logoutBtn = document.getElementById('logout-btn');
|
641 |
const spinner = document.querySelector('#workouts-list .spinner');
|
@@ -681,18 +454,88 @@
|
|
681 |
// --- DÉFINITION DES FONCTIONS ---
|
682 |
// ===========================================
|
683 |
|
684 |
-
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
function
|
693 |
-
|
694 |
-
|
695 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
696 |
function loadWorkoutTypes() { 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'); }); }
|
697 |
function renderWorkoutTypesList() { if (!workoutTypesList || !emptyTypesMessage) { console.error("DOM elements for types list missing"); 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); }); } }
|
698 |
function handleAddWorkoutType(event) { event.preventDefault(); if (!currentFirebaseUser || !newTypeNameInput || !db || !addTypeError || !addTypeForm) { console.error("Missing elements for adding workout type"); 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'; } }); }
|
@@ -717,26 +560,38 @@
|
|
717 |
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); }); } }
|
718 |
function handleFirestoreError(error) { console.error("Erreur Firestore:", error.code, error.message); alert(`Erreur de base de données: ${error.message}`); }
|
719 |
|
|
|
720 |
// --- ÉCOUTEURS D'ÉVÉNEMENTS ---
|
721 |
function initEventListeners() {
|
722 |
console.log("initEventListeners: Attachement...");
|
|
|
723 |
if (loginForm) loginForm.addEventListener('submit', handleAuthAction); else console.error("!loginForm");
|
724 |
if (authSwitchLink) authSwitchLink.addEventListener('click', authSwitchMode); else console.error("!authSwitchLink");
|
|
|
725 |
if (logoutBtn) logoutBtn.addEventListener('click', handleLogout); else console.error("!logoutBtn");
|
|
|
726 |
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`); });
|
|
|
727 |
if (newWorkoutBtn) newWorkoutBtn.addEventListener('click', () => { if (!currentFirebaseUser) return; showPage('select-workout-type-page'); }); else console.error("!newWorkoutBtn");
|
728 |
if (manageTypesBtn) manageTypesBtn.addEventListener('click', () => { if (!currentFirebaseUser) return; showPage('manage-types-page'); }); else console.error("!manageTypesBtn");
|
|
|
729 |
if (addTypeForm) addTypeForm.addEventListener('submit', handleAddWorkoutType); else console.error("!addTypeForm");
|
|
|
730 |
if (selectWorkoutTypeDropdown) selectWorkoutTypeDropdown.addEventListener('change', updateStartStructuredBtnState); else console.error("!selectWorkoutTypeDropdown");
|
731 |
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");
|
732 |
if (startFreeWorkoutBtn) startFreeWorkoutBtn.addEventListener('click', () => { clearNewWorkoutForm("Libre"); showPage('new-workout-page'); }); else console.error("!startFreeWorkoutBtn");
|
|
|
733 |
backToHomeBtns.forEach(btn => { if (btn) btn.addEventListener('click', () => showPage('home-page')); else console.error("!backToHomeBtn"); });
|
|
|
734 |
if (cancelNewWorkoutBtn) cancelNewWorkoutBtn.addEventListener('click', () => { if (confirm("Annuler cette séance ? Les données non enregistrées seront perdues.")) showPage('home-page'); }); else console.error("!cancelNewWorkoutBtn");
|
735 |
if (saveWorkoutBtn) saveWorkoutBtn.addEventListener('click', saveWorkout); else console.error("!saveWorkoutBtn");
|
736 |
if (addExerciseBtn) addExerciseBtn.addEventListener('click', handleAddNewExercise); else console.error("!addExerciseBtn");
|
|
|
737 |
if (prevExerciseBtn) prevExerciseBtn.addEventListener('click', () => navigateExerciseForm(-1)); else console.error("!prevExerciseBtn");
|
738 |
if (nextExerciseBtn) nextExerciseBtn.addEventListener('click', () => navigateExerciseForm(1)); else console.error("!nextExerciseBtn");
|
|
|
739 |
if (deleteWorkoutBtn) deleteWorkoutBtn.addEventListener('click', deleteWorkout); else console.error("!deleteWorkoutBtn");
|
|
|
740 |
if (satisfactionRange) satisfactionRange.addEventListener('input', () => { if (satisfactionValue) satisfactionValue.textContent = `${satisfactionRange.value}%`; }); else console.error("!satisfactionRange");
|
741 |
console.log("initEventListeners: Fin attachement.");
|
742 |
}
|
@@ -749,35 +604,33 @@
|
|
749 |
document.addEventListener('DOMContentLoaded', () => {
|
750 |
console.log("DOM chargé. Initialisation de l'application...");
|
751 |
if (typeof firebase !== 'undefined' && typeof firebase.initializeApp === 'function' && typeof firebase.auth === 'function' && typeof firebase.firestore === 'function') {
|
752 |
-
if (!auth) auth = firebase.auth();
|
753 |
-
if (!db) db = firebase.firestore();
|
754 |
-
|
755 |
if (auth && db) {
|
756 |
console.log("Firebase prêt. Initialisation des listeners...");
|
757 |
initEventListeners(); // Initialize event listeners first
|
758 |
initAuthListener(); // Initialize auth listener to handle state changes and initial check
|
759 |
-
//
|
760 |
-
const user = auth.currentUser;
|
761 |
if (user) {
|
762 |
console.log("Utilisateur déjà connecté trouvé:", user.uid);
|
763 |
currentFirebaseUser = user;
|
764 |
-
showApp();
|
765 |
} else {
|
766 |
console.log("Utilisateur non connecté au chargement.");
|
767 |
-
showLoginPage();
|
768 |
}
|
769 |
} else {
|
770 |
-
console.error("ERREUR CRITIQUE: Firebase Auth ou Firestore non
|
771 |
alert("Erreur critique : Services Firebase non disponibles.");
|
772 |
}
|
773 |
} else {
|
774 |
console.error("ERREUR CRITIQUE: Firebase SDK non chargé ou incomplet !");
|
775 |
alert("Erreur critique : Impossible de charger les composants Firebase.");
|
776 |
-
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)
|
777 |
}
|
778 |
-
}); // FIN
|
779 |
|
780 |
-
</script> <!-- FIN
|
781 |
|
782 |
-
</body> <!-- FIN
|
783 |
-
</html> <!-- FIN
|
|
|
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 */
|
|
|
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 |
+
header img#app-logo { max-width: 250px; height: auto; margin-bottom: 0.5rem; }
|
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; }
|
58 |
+
.btn-outline { background-color: transparent; color: var(--accent); border: 1px solid var(--accent); }
|
59 |
+
.btn-outline:hover { background-color: rgba(255, 193, 7, 0.1); color: var(--accent); }
|
60 |
+
.btn-outline:disabled { color: #555; border-color: #555; background-color: transparent; }
|
61 |
+
.btn-danger { background-color: var(--danger); color: white; border-color: var(--danger); }
|
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); }
|
70 |
+
label { display: block; margin-bottom: 0.4rem; color: var(--text-secondary); font-size: 0.9rem; font-weight: bold; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; }
|
96 |
+
.workout-header h3 { color: var(--text-light); font-family: var(--font-primary); text-transform: none; letter-spacing: normal; font-weight: bold; flex-grow: 1; min-width: 0; position: static; margin-bottom: 0; }
|
97 |
+
.workout-header > .badge { background-color: #2a2a2a; color: var(--text-secondary); padding: 0.3rem 0.6rem; border-radius: 4px; font-size: 0.8rem; font-weight: normal; white-space: nowrap; flex-shrink: 0; margin-left: auto; position: static; }
|
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; }
|
|
|
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; } /* Container for switch and forgot password */
|
144 |
+
.auth-switch { margin-bottom: 0.5rem; color: var(--text-secondary); } /* Style for "Pas de compte..." */
|
145 |
+
.auth-options a { color: var(--accent); cursor: pointer; text-decoration: underline; font-weight: bold; }
|
146 |
+
#forgot-password-link { display: block; /* Place it below the switch link */ }
|
147 |
+
|
148 |
|
149 |
/* User Info */
|
150 |
.user-info { text-align: right; margin-bottom: 1rem; font-size: 0.9rem; color: var(--text-secondary);}
|
|
|
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; }
|
|
|
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; }
|
170 |
.type-trend-card li { margin-bottom: 0.6rem; color: var(--text-light); display: flex; justify-content: space-between; border-bottom: 1px dashed #333; padding-bottom: 0.4rem;}
|
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 */
|
|
|
180 |
.form-row { flex-direction: column; gap: 0.8rem; }
|
181 |
.flex-between { flex-direction: column; align-items: stretch; gap: 0.8rem; }
|
182 |
.flex-between > div { width: 100%; display: flex; justify-content: center; margin-top: 0.5rem;}
|
183 |
+
.workout-header { flex-wrap: wrap; }
|
184 |
+
.workout-header h3 { margin-bottom: 0.3rem; }
|
185 |
+
.workout-header > .badge { margin-left: 0; }
|
|
|
|
|
186 |
.user-info { text-align: center; margin-bottom: 0.8rem;}
|
187 |
.user-info button { display: block; margin: 0.5rem auto 0;}
|
188 |
.exercise-navigation button { padding: 0.6rem; font-size: 0.9rem;}
|
|
|
196 |
</head>
|
197 |
<body>
|
198 |
<div class="container">
|
199 |
+
<!-- Header -->
|
200 |
<header>
|
|
|
201 |
<img src="input_file_0.png" alt="Cocktail Fitness Logo" id="app-logo">
|
202 |
<p>Votre suivi de séances personnalisé</p>
|
203 |
</header>
|
|
|
206 |
<div id="login-page">
|
207 |
<div class="card">
|
208 |
<h2 id="auth-title">Connexion</h2>
|
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>
|
226 |
</div>
|
227 |
|
|
|
233 |
</div>
|
234 |
|
235 |
<div id="app-container">
|
236 |
+
<!-- Contenu des différentes pages (Home, Manage Types, etc.) -->
|
237 |
<!-- Home Page -->
|
238 |
<div id="home-page">
|
239 |
<div class="flex-between">
|
|
|
380 |
<!-- ================= JAVASCRIPT =================== -->
|
381 |
<!-- ================================================== -->
|
382 |
<script>
|
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 {
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
399 |
|
400 |
// --- Éléments DOM ---
|
401 |
const loginPage = document.getElementById('login-page');
|
|
|
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'); // **NOUVEAU**
|
409 |
const authTitle = document.getElementById('auth-title');
|
410 |
const authSwitchText = document.getElementById('auth-switch-text');
|
411 |
+
const loginMessage = document.getElementById('login-message'); // **MODIFIÉ** (remplace loginError)
|
412 |
const currentUserDisplay = document.getElementById('current-user-display');
|
413 |
const logoutBtn = document.getElementById('logout-btn');
|
414 |
const spinner = document.querySelector('#workouts-list .spinner');
|
|
|
454 |
// --- DÉFINITION DES FONCTIONS ---
|
455 |
// ===========================================
|
456 |
|
457 |
+
function showMessage(message, isError = true) {
|
458 |
+
if (loginMessage) {
|
459 |
+
loginMessage.textContent = message;
|
460 |
+
loginMessage.className = isError ? 'error' : 'success'; // Applique la classe CSS
|
461 |
+
loginMessage.style.display = 'block';
|
462 |
+
}
|
463 |
+
}
|
464 |
+
|
465 |
+
function clearMessage() {
|
466 |
+
if (loginMessage) {
|
467 |
+
loginMessage.textContent = '';
|
468 |
+
loginMessage.style.display = 'none';
|
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 |
+
// **NOUVELLE FONCTION** pour la réinitialisation du mot de passe
|
490 |
+
function handlePasswordReset(event) {
|
491 |
+
event.preventDefault(); // Empêche le lien de naviguer
|
492 |
+
if (!auth) { alert("Service d'authentification non disponible."); return; }
|
493 |
+
clearMessage(); // Efface les messages précédents
|
494 |
+
|
495 |
+
const email = prompt("Veuillez entrer votre adresse email pour recevoir le lien de réinitialisation :");
|
496 |
+
|
497 |
+
if (!email) { // Si l'utilisateur annule ou ne saisit rien
|
498 |
+
showMessage("Saisie de l'email annulée.", true);
|
499 |
+
return;
|
500 |
+
}
|
501 |
+
|
502 |
+
console.log("Tentative d'envoi de l'email de réinitialisation à :", email);
|
503 |
+
auth.sendPasswordResetEmail(email)
|
504 |
+
.then(() => {
|
505 |
+
console.log("Email de réinitialisation envoyé.");
|
506 |
+
showMessage("Si un compte existe pour " + email + ", un email de réinitialisation a été envoyé.", false); // Message succès (vert)
|
507 |
+
})
|
508 |
+
.catch((error) => {
|
509 |
+
console.error("Erreur sendPasswordResetEmail:", error);
|
510 |
+
// Utilise handleAuthError pour afficher un message standardisé si possible
|
511 |
+
handleAuthError(error); // Affiche l'erreur dans la zone de message
|
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 getAuthErrorMessage(error) { /* ... identique ... */ 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 (vérifiez Console Firebase).'; 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.`; } } // Ajout case 'auth/missing-email'
|
537 |
+
|
538 |
+
// ... (Le reste des fonctions JS : loadWorkoutTypes, renderWorkoutTypesList, handleAddWorkoutType, ..., handleFirestoreError reste identique) ...
|
539 |
function loadWorkoutTypes() { 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'); }); }
|
540 |
function renderWorkoutTypesList() { if (!workoutTypesList || !emptyTypesMessage) { console.error("DOM elements for types list missing"); 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); }); } }
|
541 |
function handleAddWorkoutType(event) { event.preventDefault(); if (!currentFirebaseUser || !newTypeNameInput || !db || !addTypeError || !addTypeForm) { console.error("Missing elements for adding workout type"); 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'; } }); }
|
|
|
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"); // **NOUVEAU**
|
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`); });
|
574 |
+
// Workout List Actions
|
575 |
if (newWorkoutBtn) newWorkoutBtn.addEventListener('click', () => { if (!currentFirebaseUser) return; showPage('select-workout-type-page'); }); else console.error("!newWorkoutBtn");
|
576 |
if (manageTypesBtn) manageTypesBtn.addEventListener('click', () => { if (!currentFirebaseUser) return; showPage('manage-types-page'); }); else console.error("!manageTypesBtn");
|
577 |
+
// Type Management
|
578 |
if (addTypeForm) addTypeForm.addEventListener('submit', handleAddWorkoutType); else console.error("!addTypeForm");
|
579 |
+
// Select Type Page
|
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 |
backToHomeBtns.forEach(btn => { if (btn) btn.addEventListener('click', () => showPage('home-page')); else console.error("!backToHomeBtn"); });
|
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('home-page'); }); else console.error("!cancelNewWorkoutBtn");
|
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
|
590 |
if (prevExerciseBtn) prevExerciseBtn.addEventListener('click', () => navigateExerciseForm(-1)); else console.error("!prevExerciseBtn");
|
591 |
if (nextExerciseBtn) nextExerciseBtn.addEventListener('click', () => navigateExerciseForm(1)); else console.error("!nextExerciseBtn");
|
592 |
+
// Details Page
|
593 |
if (deleteWorkoutBtn) deleteWorkoutBtn.addEventListener('click', deleteWorkout); else console.error("!deleteWorkoutBtn");
|
594 |
+
// Satisfaction
|
595 |
if (satisfactionRange) satisfactionRange.addEventListener('input', () => { if (satisfactionValue) satisfactionValue.textContent = `${satisfactionRange.value}%`; }); else console.error("!satisfactionRange");
|
596 |
console.log("initEventListeners: Fin attachement.");
|
597 |
}
|
|
|
604 |
document.addEventListener('DOMContentLoaded', () => {
|
605 |
console.log("DOM chargé. Initialisation de l'application...");
|
606 |
if (typeof firebase !== 'undefined' && typeof firebase.initializeApp === 'function' && typeof firebase.auth === 'function' && typeof firebase.firestore === 'function') {
|
607 |
+
if (!auth) auth = firebase.auth();
|
608 |
+
if (!db) db = firebase.firestore();
|
|
|
609 |
if (auth && db) {
|
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 SDK non chargé ou incomplet !");
|
628 |
alert("Erreur critique : Impossible de charger les composants Firebase.");
|
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 |
|
633 |
+
</script> <!-- FIN SCRIPT -->
|
634 |
|
635 |
+
</body> <!-- FIN BODY -->
|
636 |
+
</html> <!-- FIN HTML -->
|