BitDown commited on
Commit
ddd7255
·
verified ·
1 Parent(s): 1bef68b

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +708 -18
index.html CHANGED
@@ -1,21 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <div id="app-container">
2
  <!-- Page Accueil / Liste Séances -->
3
- <!-- SEULE CELLE-CI A 'active' AU DÉBUT -->
4
  <div id="home-page" class="active">
5
  <div class="flex-between">
6
  <h2>Mes séances</h2>
7
  <button id="new-workout-btn" class="btn">Nouvelle séance</button>
8
  </div>
9
- <div id="workouts-list" class="workout-list">
10
- <div class="spinner"></div>
 
11
  <p id="empty-workout-message" class="hidden" style="text-align: center; margin-top: 2rem;">
12
  Aucune séance enregistrée pour cet utilisateur.
13
  </p>
14
  </div>
15
  </div>
16
 
17
- <!-- Page Nouvelle Séance -->
18
- <!-- !!! ENLEVER class="hidden" !!! -->
19
  <div id="new-workout-page">
20
  <div class="flex-between">
21
  <h2>Nouvelle séance</h2>
@@ -24,8 +137,7 @@
24
  <button id="save-workout-btn" class="btn">Enregistrer</button>
25
  </div>
26
  </div>
27
- <!-- ... reste du contenu de new-workout-page ... -->
28
- <div class="card">
29
  <div class="form-group">
30
  <label for="workout-name">Nom de la séance</label>
31
  <input type="text" id="workout-name" placeholder="Ex: Push, Legs, Full Body...">
@@ -42,7 +154,9 @@
42
  </div>
43
  </div>
44
  <h3 style="margin: 1rem 0;">Exercices</h3>
45
- <div id="exercises-container"></div>
 
 
46
  <button id="add-exercise-btn" class="btn btn-outline" style="width: 100%; margin-top: 1rem;">+ Ajouter Exercice</button>
47
  <div class="card" style="margin-top: 2rem;">
48
  <div class="form-group">
@@ -56,15 +170,13 @@
56
  </div>
57
  </div>
58
 
59
- <!-- Page Détail Séance -->
60
- <!-- !!! ENLEVER class="hidden" !!! -->
61
  <div id="workout-details-page">
62
  <div class="flex-between">
63
  <h2 id="detail-workout-name">Détail Séance</h2>
64
  <button id="back-to-home" class="btn btn-outline">Retour</button>
65
  </div>
66
- <!-- ... reste du contenu de workout-details-page ... -->
67
- <div class="card">
68
  <div class="workout-details-info">
69
  <div class="form-row">
70
  <p><strong>Date:</strong> <span id="detail-date"></span></p>
@@ -87,16 +199,16 @@
87
  </div>
88
  </div>
89
  <h3 style="margin: 1.5rem 0 1rem;">Exercices Réalisés</h3>
90
- <div id="detail-exercises-container"></div>
 
 
91
  <button id="delete-workout-btn" class="btn btn-danger" style="width: 100%; margin-top: 2rem;">Supprimer cette séance</button>
92
  </div>
93
 
94
- <!-- Page Statistiques -->
95
- <!-- !!! ENLEVER class="hidden" !!! -->
96
  <div id="stats-page">
97
  <h2>Statistiques</h2>
98
- <!-- ... reste du contenu de stats-page ... -->
99
- <div class="stats-grid">
100
  <div class="card stat-card">
101
  <div class="stat-value" id="stats-workout-count">0</div>
102
  <div>Séances Totales</div>
@@ -117,4 +229,582 @@
117
  </p>
118
  </div>
119
  </div>
120
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>LiftTrack - Suivi Musculation Multi-Utilisateurs</title>
7
+ <style>
8
+ /* --- Styles CSS --- */
9
+ :root {
10
+ --bg-dark: #121212;
11
+ --bg-card: #1e1e1e;
12
+ --text-light: #e0e0e0;
13
+ --accent: #4CAF50;
14
+ --accent-dark: #3a8a3d;
15
+ --danger: #f44336;
16
+ }
17
+ * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
18
+ body { background-color: var(--bg-dark); color: var(--text-light); min-height: 100vh; padding-bottom: 80px; /* Espace pour nav bottom */ }
19
+ .container { width: 100%; max-width: 800px; margin: 0 auto; padding: 1rem; }
20
+ header { padding: 1rem 0; text-align: center; border-bottom: 1px solid #333; margin-bottom: 1rem; }
21
+ h1, h2, h3 { color: var(--accent); }
22
+ .btn { background-color: var(--accent); color: white; border: none; padding: 0.6rem 1.2rem; border-radius: 4px; cursor: pointer; font-weight: bold; transition: background-color 0.2s; }
23
+ .btn:hover { background-color: var(--accent-dark); }
24
+ .btn-outline { background-color: transparent; color: var(--accent); border: 1px solid var(--accent); }
25
+ .btn-danger { background-color: var(--danger); }
26
+ input, select, textarea { width: 100%; padding: 0.6rem; margin-bottom: 1rem; background-color: #2a2a2a; border: 1px solid #444; border-radius: 4px; color: var(--text-light); }
27
+ input[type="checkbox"] { width: auto; margin-right: 0.5rem; vertical-align: middle; } /* Align checkbox */
28
+ .card { background-color: var(--bg-card); border-radius: 8px; padding: 1rem; margin-bottom: 1rem; box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
29
+ .form-group { margin-bottom: 1rem; }
30
+ .form-row { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; /* Allow wrapping on small screens */ }
31
+ .form-row > * { flex: 1; margin-bottom: 0; min-width: 100px; /* Prevent items from becoming too small */ }
32
+ label { display: block; margin-bottom: 0.3rem; color: #bbb; font-size: 0.9rem; }
33
+ .exercise { border-left: 3px solid var(--accent); padding-left: 1rem; margin-bottom: 1.5rem; }
34
+ .exercise-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; gap: 0.5rem;}
35
+ .exercise-header input {margin-bottom: 0;} /* Remove margin on input in header */
36
+ .series-container { margin-left: 0.5rem; margin-top: 0.5rem; }
37
+ .series { background-color: #252525; padding: 0.7rem; border-radius: 4px; margin-bottom: 0.5rem; }
38
+ .nav-bottom { position: fixed; bottom: 0; left: 0; width: 100%; background-color: #1a1a1a; display: flex; justify-content: space-around; padding: 0.7rem 0; box-shadow: 0 -2px 10px rgba(0,0,0,0.3); z-index: 10; }
39
+ .nav-item { text-align: center; color: #888; text-decoration: none; font-size: 0.85rem; transition: color 0.2s; padding: 0 0.5rem;}
40
+ .nav-item.active { color: var(--accent); }
41
+ .nav-icon { font-size: 1.4rem; margin-bottom: 0.2rem; }
42
+ .workout-card { border-left: 3px solid var(--accent); cursor: pointer; transition: transform 0.2s; }
43
+ .workout-card:hover { transform: translateX(5px); }
44
+ .workout-header { display: flex; justify-content: space-between; align-items: flex-start; }
45
+ .stat-card { text-align: center; padding: 1rem; }
46
+ .stat-value { font-size: 1.8rem; color: var(--accent); font-weight: bold; }
47
+ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 1rem; }
48
+
49
+ /* --- Règles d'Affichage Cruciales --- */
50
+ .hidden { display: none; } /* Utilitaire pour cacher des éléments spécifiques */
51
+ #login-page { display: block; } /* Montrer la page de login au début */
52
+ #main-app-content { display: none; } /* Cacher le contenu principal au début */
53
+ #app-container > div:not(.active) {
54
+ display: none !important; /* Cache les divs enfants non actives de app-container (CORRIGÉ avec !important) */
55
+ }
56
+ /* --- Fin Règles d'Affichage --- */
57
+
58
+ .flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;} /* Allow wrapping */
59
+ .badge { background-color: var(--accent); color: white; padding: 0.2rem 0.5rem; border-radius: 10px; font-size: 0.8rem; white-space: nowrap; }
60
+ .spinner { border: 4px solid rgba(0, 0, 0, 0.1); width: 36px; height: 36px; border-radius: 50%; border-left-color: var(--accent); animation: spin 1s linear infinite; margin: 2rem auto; display: none; }
61
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
62
+ .exercise-summary { margin: 0.3rem 0; padding: 0.3rem 0; border-bottom: 1px solid #333; font-size: 0.9rem;}
63
+ .exercise-summary:last-child { border-bottom: none; }
64
+ .satisfaction { display: flex; align-items: center; justify-content: center; flex-direction: column; margin-top: 1rem; }
65
+ .satisfaction-value { font-size: 2rem; color: var(--accent); margin-top: 0.5rem; }
66
+ @media (max-width: 600px) {
67
+ .form-row { flex-direction: column; gap: 0; }
68
+ .container { padding: 0.5rem; }
69
+ h1 { font-size: 1.5rem; }
70
+ .flex-between { flex-direction: column; align-items: stretch; } /* Stack items vertically */
71
+ .flex-between > div { width: 100%; display: flex; justify-content: flex-end; margin-top: 0.5rem;} /* Push buttons to the right */
72
+ .user-info { text-align: center; margin-bottom: 0.5rem;}
73
+ .user-info button { display: block; margin: 0.5rem auto 0;} /* Center logout button */
74
+ }
75
+ /* Styles page de connexion */
76
+ #login-page .card { max-width: 400px; margin: 2rem auto; }
77
+ #login-page h2 { text-align: center; margin-bottom: 1.5rem; }
78
+ #login-error { color: var(--danger); text-align: center; margin-top: 1rem; display: none; }
79
+ .user-info { text-align: right; margin-bottom: 1rem; font-size: 0.9rem; color: #bbb;}
80
+ .user-info strong { color: var(--text-light); }
81
+ .user-info button { margin-left: 0.5rem; padding: 0.2rem 0.5rem; font-size: 0.8rem; }
82
+ </style>
83
+ </head>
84
+ <body>
85
+ <div class="container">
86
+ <header>
87
+ <h1>LiftTrack</h1>
88
+ <p>Suivi de vos séances de musculation</p>
89
+ </header>
90
+
91
+ <!-- Écran de Connexion -->
92
+ <div id="login-page">
93
+ <div class="card">
94
+ <h2>Accès Utilisateur</h2>
95
+ <div class="form-group">
96
+ <label for="username">Nom d'utilisateur</label>
97
+ <input type="text" id="username" placeholder="Entrez votre nom ou pseudo">
98
+ </div>
99
+ <button id="login-btn" class="btn" style="width: 100%;">Accéder / Créer</button>
100
+ <p id="login-error">Nom d'utilisateur invalide.</p>
101
+ <p style="font-size: 0.8rem; color: #888; text-align: center; margin-top: 1rem;">
102
+ Entrez un nom pour accéder à vos données ou créer un nouvel espace si le nom n'existe pas.
103
+ </p>
104
+ </div>
105
+ </div>
106
+
107
+ <!-- Contenu Principal de l'Application (initiallement caché) -->
108
+ <div id="main-app-content">
109
+ <div class="user-info">
110
+ Connecté: <strong id="current-user-display"></strong>
111
+ <button id="logout-btn" class="btn btn-outline">Changer</button>
112
+ </div>
113
+
114
  <div id="app-container">
115
  <!-- Page Accueil / Liste Séances -->
116
+ <!-- Seule cette page a la classe 'active' au début -->
117
  <div id="home-page" class="active">
118
  <div class="flex-between">
119
  <h2>Mes séances</h2>
120
  <button id="new-workout-btn" class="btn">Nouvelle séance</button>
121
  </div>
122
+ <div id="workouts-list" class="workout-list" style="margin-top: 1rem;">
123
+ <!-- Liste des séances ici -->
124
+ <div class="spinner hidden"></div> <!-- Spinner caché au début -->
125
  <p id="empty-workout-message" class="hidden" style="text-align: center; margin-top: 2rem;">
126
  Aucune séance enregistrée pour cet utilisateur.
127
  </p>
128
  </div>
129
  </div>
130
 
131
+ <!-- Page Nouvelle Séance (PAS de classe 'hidden' ici) -->
 
132
  <div id="new-workout-page">
133
  <div class="flex-between">
134
  <h2>Nouvelle séance</h2>
 
137
  <button id="save-workout-btn" class="btn">Enregistrer</button>
138
  </div>
139
  </div>
140
+ <div class="card">
 
141
  <div class="form-group">
142
  <label for="workout-name">Nom de la séance</label>
143
  <input type="text" id="workout-name" placeholder="Ex: Push, Legs, Full Body...">
 
154
  </div>
155
  </div>
156
  <h3 style="margin: 1rem 0;">Exercices</h3>
157
+ <div id="exercises-container">
158
+ <!-- Exercices ajoutés dynamiquement ici -->
159
+ </div>
160
  <button id="add-exercise-btn" class="btn btn-outline" style="width: 100%; margin-top: 1rem;">+ Ajouter Exercice</button>
161
  <div class="card" style="margin-top: 2rem;">
162
  <div class="form-group">
 
170
  </div>
171
  </div>
172
 
173
+ <!-- Page Détail Séance (PAS de classe 'hidden' ici) -->
 
174
  <div id="workout-details-page">
175
  <div class="flex-between">
176
  <h2 id="detail-workout-name">Détail Séance</h2>
177
  <button id="back-to-home" class="btn btn-outline">Retour</button>
178
  </div>
179
+ <div class="card">
 
180
  <div class="workout-details-info">
181
  <div class="form-row">
182
  <p><strong>Date:</strong> <span id="detail-date"></span></p>
 
199
  </div>
200
  </div>
201
  <h3 style="margin: 1.5rem 0 1rem;">Exercices Réalisés</h3>
202
+ <div id="detail-exercises-container">
203
+ <!-- Détails des exercices affichés ici -->
204
+ </div>
205
  <button id="delete-workout-btn" class="btn btn-danger" style="width: 100%; margin-top: 2rem;">Supprimer cette séance</button>
206
  </div>
207
 
208
+ <!-- Page Statistiques (PAS de classe 'hidden' ici) -->
 
209
  <div id="stats-page">
210
  <h2>Statistiques</h2>
211
+ <div class="stats-grid">
 
212
  <div class="card stat-card">
213
  <div class="stat-value" id="stats-workout-count">0</div>
214
  <div>Séances Totales</div>
 
229
  </p>
230
  </div>
231
  </div>
232
+ </div>
233
+
234
+ <!-- Menu Navigation Bas -->
235
+ <nav class="nav-bottom">
236
+ <a href="#" class="nav-item active" data-page="home-page">
237
+ <div class="nav-icon">📋</div>
238
+ <div>Séances</div>
239
+ </a>
240
+ <a href="#" class="nav-item" data-page="stats-page">
241
+ <div class="nav-icon">📊</div>
242
+ <div>Stats</div>
243
+ </a>
244
+ </nav>
245
+ </div> <!-- Fin de #main-app-content -->
246
+ </div> <!-- Fin de .container -->
247
+
248
+ <script>
249
+ // --- Variables d'État ---
250
+ let workouts = []; // Séances pour l'utilisateur courant
251
+ let currentUser = null; // Nom de l'utilisateur connecté
252
+ let currentWorkoutId = null; // ID de la séance en cours de visualisation/modification
253
+
254
+ // --- Éléments DOM ---
255
+ const loginPage = document.getElementById('login-page');
256
+ const mainAppContent = document.getElementById('main-app-content');
257
+ const usernameInput = document.getElementById('username');
258
+ const loginBtn = document.getElementById('login-btn');
259
+ const loginError = document.getElementById('login-error');
260
+ const currentUserDisplay = document.getElementById('current-user-display');
261
+ const logoutBtn = document.getElementById('logout-btn');
262
+ const spinner = document.querySelector('#workouts-list .spinner');
263
+
264
+ const appContainer = document.getElementById('app-container');
265
+ const navItems = document.querySelectorAll('.nav-item');
266
+ const newWorkoutBtn = document.getElementById('new-workout-btn');
267
+ const saveWorkoutBtn = document.getElementById('save-workout-btn');
268
+ const cancelNewWorkoutBtn = document.getElementById('cancel-new-workout-btn');
269
+ const addExerciseBtn = document.getElementById('add-exercise-btn');
270
+ const exercisesContainer = document.getElementById('exercises-container'); // Pour la page nouvelle séance
271
+ const workoutsList = document.getElementById('workouts-list'); // Pour afficher la liste
272
+ const backToHomeBtn = document.getElementById('back-to-home');
273
+ const deleteWorkoutBtn = document.getElementById('delete-workout-btn');
274
+ const satisfactionRange = document.getElementById('satisfaction');
275
+ const satisfactionValue = document.querySelector('.satisfaction-value');
276
+ const emptyWorkoutMessage = document.getElementById('empty-workout-message');
277
+ const workoutDateInput = document.getElementById('workout-date');
278
+
279
+
280
+ // --- Initialisation ---
281
+ document.addEventListener('DOMContentLoaded', () => {
282
+ const rememberedUser = sessionStorage.getItem('liftTrackCurrentUser');
283
+ if (rememberedUser) {
284
+ loginUser(rememberedUser);
285
+ } else {
286
+ showLoginPage();
287
+ }
288
+ initEventListeners();
289
+ // setTodayDate sera appelé dans showApp après la connexion
290
+ });
291
+
292
+ // --- Fonctions d'Authentification ---
293
+ function showLoginPage() {
294
+ loginPage.style.display = 'block';
295
+ mainAppContent.style.display = 'none';
296
+ loginError.style.display = 'none';
297
+ usernameInput.value = '';
298
+ currentUser = null; // Assurer que l'utilisateur est bien déconnecté
299
+ }
300
+
301
+ function showApp() {
302
+ if (!currentUser) return; // Ne rien faire si pas d'utilisateur défini
303
+ loginPage.style.display = 'none';
304
+ mainAppContent.style.display = 'block';
305
+ currentUserDisplay.textContent = currentUser;
306
+ setTodayDate(); // Mettre la date du jour par défaut
307
+ loadWorkouts(); // Charger les données de l'utilisateur
308
+ renderWorkoutsList(); // Afficher ses séances
309
+ showPage('home-page'); // Afficher la page d'accueil par défaut
310
+ }
311
+
312
+ function handleLogin() {
313
+ const username = usernameInput.value.trim();
314
+ // Validation simple: non vide
315
+ if (username && username.length > 0) {
316
+ loginUser(username);
317
+ } else {
318
+ loginError.textContent = "Veuillez entrer un nom d'utilisateur.";
319
+ loginError.style.display = 'block';
320
+ }
321
+ }
322
+
323
+ function loginUser(username) {
324
+ currentUser = username;
325
+ sessionStorage.setItem('liftTrackCurrentUser', currentUser); // Mémoriser pour la session
326
+ showApp(); // Afficher l'application principale
327
+ }
328
+
329
+ function handleLogout() {
330
+ currentUser = null;
331
+ sessionStorage.removeItem('liftTrackCurrentUser');
332
+ workouts = []; // Vider les données en mémoire
333
+ showLoginPage(); // Revenir à la page de connexion
334
+ }
335
+
336
+ // --- Fonctions de Persistance des Données (Spécifique à l'utilisateur) ---
337
+ function getStorageKey() {
338
+ if (!currentUser) return null;
339
+ const safeUsername = currentUser.replace(/[^a-zA-Z0-9_-]/g, '_'); // Rendre le nom sûr pour une clé
340
+ return `liftTrackData_${safeUsername}`;
341
+ }
342
+
343
+ function loadWorkouts() {
344
+ const storageKey = getStorageKey();
345
+ if (!storageKey) {
346
+ workouts = []; // Pas d'utilisateur, pas de données
347
+ return;
348
+ }
349
+ // Simuler un petit chargement
350
+ if (spinner) spinner.classList.remove('hidden');
351
+ emptyWorkoutMessage.classList.add('hidden'); // Cacher le message pendant le chargement
352
+ workoutsList.innerHTML = ''; // Vider la liste actuelle
353
+
354
+ setTimeout(() => { // Simule un délai réseau ou de traitement
355
+ try {
356
+ const savedData = localStorage.getItem(storageKey);
357
+ workouts = savedData ? JSON.parse(savedData) : [];
358
+ console.log(`Chargé ${workouts.length} séances pour ${currentUser}`);
359
+ } catch (e) {
360
+ console.error("Erreur lors du parsing des données depuis localStorage:", e);
361
+ workouts = []; // En cas d'erreur de parsing, repartir de zéro
362
+ alert("Erreur lors du chargement de vos données. Certaines données pourraient être corrompues.");
363
+ } finally {
364
+ if (spinner) spinner.classList.add('hidden');
365
+ renderWorkoutsList(); // Afficher la liste (même si vide) après chargement
366
+ }
367
+ }, 150); // Petit délai pour voir le spinner
368
+ }
369
+
370
+
371
+ function saveWorkouts() {
372
+ const storageKey = getStorageKey();
373
+ if (!storageKey) {
374
+ console.error("Sauvegarde impossible: aucun utilisateur connecté.");
375
+ return;
376
+ }
377
+ try {
378
+ localStorage.setItem(storageKey, JSON.stringify(workouts));
379
+ console.log(`Sauvegardé ${workouts.length} séances pour ${currentUser}`);
380
+ } catch (e) {
381
+ console.error("Erreur lors de la sauvegarde dans localStorage:", e);
382
+ alert("Erreur lors de la sauvegarde de vos données. Le stockage local est peut-être plein ou indisponible.");
383
+ }
384
+ }
385
+
386
+ // --- Écouteurs d'Événements ---
387
+ function initEventListeners() {
388
+ loginBtn.addEventListener('click', handleLogin);
389
+ logoutBtn.addEventListener('click', handleLogout);
390
+ usernameInput.addEventListener('keypress', (e) => {
391
+ if (e.key === 'Enter') {
392
+ e.preventDefault(); // Empêche soumission formulaire (si jamais il y en avait un)
393
+ handleLogin();
394
+ }
395
+ });
396
+
397
+ // Navigation principale (menu du bas)
398
+ navItems.forEach(item => {
399
+ item.addEventListener('click', (e) => {
400
+ e.preventDefault();
401
+ if (!currentUser) return; // Sécurité
402
+ const targetPageId = item.getAttribute('data-page');
403
+ showPage(targetPageId);
404
+ // Mettre à jour l'état actif du menu
405
+ navItems.forEach(nav => nav.classList.remove('active'));
406
+ item.classList.add('active');
407
+ // Mettre à jour les stats si on va sur la page stats
408
+ if (targetPageId === 'stats-page') {
409
+ updateStats();
410
+ }
411
+ });
412
+ });
413
+
414
+ // Boutons de l'application principale
415
+ newWorkoutBtn.addEventListener('click', () => {
416
+ if (!currentUser) return;
417
+ showPage('new-workout-page');
418
+ clearNewWorkoutForm();
419
+ currentWorkoutId = null; // Important pour indiquer une nouvelle séance
420
+ });
421
+
422
+ cancelNewWorkoutBtn.addEventListener('click', () => {
423
+ showPage('home-page'); // Retour simple à l'accueil
424
+ });
425
+
426
+ saveWorkoutBtn.addEventListener('click', saveWorkout);
427
+ addExerciseBtn.addEventListener('click', addExercise); // Note: addExercise ajoute aussi la première série
428
+ backToHomeBtn.addEventListener('click', () => showPage('home-page'));
429
+ deleteWorkoutBtn.addEventListener('click', deleteWorkout);
430
+
431
+ // Slider satisfaction
432
+ satisfactionRange.addEventListener('input', () => {
433
+ satisfactionValue.textContent = `${satisfactionRange.value}%`;
434
+ });
435
+ }
436
+
437
+ // --- Logique Principale de l'Application ---
438
+ function setTodayDate() {
439
+ if(workoutDateInput) {
440
+ try {
441
+ const today = new Date().toISOString().split('T')[0];
442
+ workoutDateInput.value = today;
443
+ } catch (e) {
444
+ console.error("Impossible de définir la date d'aujourd'hui:", e);
445
+ // Fallback si toISOString n'est pas supporté (très improbable)
446
+ workoutDateInput.value = '';
447
+ }
448
+ }
449
+ }
450
+
451
+ function showPage(pageId) {
452
+ if (!currentUser) { showLoginPage(); return; } // Rediriger si pas connecté
453
+
454
+ // Cacher toutes les pages
455
+ document.querySelectorAll('#app-container > div').forEach(page => {
456
+ page.classList.remove('active');
457
+ });
458
+
459
+ // Afficher la page cible
460
+ const pageToShow = document.getElementById(pageId);
461
+ if (pageToShow) {
462
+ pageToShow.classList.add('active');
463
+ } else {
464
+ console.error(`Page ID "${pageId}" non trouvée. Affichage de home-page.`);
465
+ document.getElementById('home-page').classList.add('active');
466
+ pageId = 'home-page'; // Corriger pageId pour la nav
467
+ }
468
+
469
+ // Mettre à jour la nav du bas
470
+ navItems.forEach(item => {
471
+ if (item.getAttribute('data-page') === pageId) {
472
+ item.classList.add('active');
473
+ } else {
474
+ item.classList.remove('active');
475
+ }
476
+ });
477
+
478
+ // Si la page cible est 'home-page', on s'assure que la nav 'Séances' est active
479
+ if (pageId === 'home-page' || pageId === 'new-workout-page' || pageId === 'workout-details-page') {
480
+ document.querySelector('.nav-item[data-page="home-page"]').classList.add('active');
481
+ document.querySelector('.nav-item[data-page="stats-page"]').classList.remove('active');
482
+ } else if (pageId === 'stats-page') {
483
+ document.querySelector('.nav-item[data-page="home-page"]').classList.remove('active');
484
+ document.querySelector('.nav-item[data-page="stats-page"]').classList.add('active');
485
+ }
486
+
487
+ }
488
+
489
+ function addExercise() {
490
+ if (!currentUser) return;
491
+ const exerciseId = `exercise-${Date.now()}`;
492
+ const exerciseDiv = document.createElement('div');
493
+ // Utiliser 'card exercise' pour le style et l'identification
494
+ exerciseDiv.className = 'card exercise';
495
+ exerciseDiv.setAttribute('data-exercise-id', exerciseId);
496
+
497
+ exerciseDiv.innerHTML = `
498
+ <div class="exercise-header">
499
+ <input type="text" placeholder="Nom Exercice" class="exercise-name">
500
+ <button class="btn btn-danger remove-exercise" style="padding: 0.3rem 0.6rem; flex-shrink: 0;">×</button>
501
+ </div>
502
+ <div class="form-group" style="margin-top: 0.5rem; margin-bottom: 0.5rem;">
503
+ <label style="display: inline-flex; align-items: center; color: #bbb; font-size: 0.9rem;">
504
+ <input type="checkbox" class="unilateral-checkbox"> Unilatéral
505
+ </label>
506
+ </div>
507
+ <div class="series-container">
508
+ <!-- Séries ajoutées ici -->
509
+ </div>
510
+ <button class="btn btn-outline add-series" style="width: 100%; margin-top: 0.5rem; padding: 0.4rem;">+ Ajouter Série</button>
511
+ `;
512
+ exercisesContainer.appendChild(exerciseDiv);
513
+
514
+ // Écouteurs pour cet exercice spécifique
515
+ const removeBtn = exerciseDiv.querySelector('.remove-exercise');
516
+ removeBtn.addEventListener('click', () => exerciseDiv.remove());
517
+
518
+ const addSeriesBtn = exerciseDiv.querySelector('.add-series');
519
+ const seriesContainer = exerciseDiv.querySelector('.series-container');
520
+ addSeriesBtn.addEventListener('click', () => addSeries(seriesContainer));
521
+
522
+ // Ajouter la première série automatiquement
523
+ addSeries(seriesContainer);
524
+ }
525
+
526
+ function addSeries(container) {
527
+ if (!currentUser || !container) return;
528
+ const seriesId = `series-${Date.now()}`;
529
+ const seriesDiv = document.createElement('div');
530
+ seriesDiv.className = 'series';
531
+ seriesDiv.setAttribute('data-series-id', seriesId);
532
+
533
+ seriesDiv.innerHTML = `
534
+ <div class="form-row" style="align-items: flex-end; gap: 0.8rem;"> <!-- Ajout gap -->
535
+ <div class="form-group" style="flex: 1.2;">
536
+ <label>Reps</label>
537
+ <input type="number" class="reps" min="1" placeholder="10" style="padding: 0.4rem;">
538
+ </div>
539
+ <div class="form-group" style="flex: 1.2;">
540
+ <label>Charge (kg)</label>
541
+ <input type="number" class="weight" min="0" step="0.1" placeholder="20" style="padding: 0.4rem;">
542
+ </div>
543
+ <div class="form-group" style="flex: 1; display: flex; align-items: center; padding-bottom: 0.6rem; min-width: 100px;">
544
+ <label style="display: inline-flex; align-items: center; color: #bbb; font-size: 0.8rem; margin-bottom: 0; white-space: nowrap;">
545
+ <input type="checkbox" class="degressive-checkbox" style="margin-right: 0.3rem;"> Dégressive
546
+ </label>
547
+ </div>
548
+ <button class="btn btn-danger remove-series" style="padding: 0.3rem 0.6rem; margin-bottom: 0.6rem; flex-basis: 30px; flex-grow: 0; align-self: center;">×</button>
549
+ </div>
550
+ `;
551
+ container.appendChild(seriesDiv);
552
+
553
+ const removeBtn = seriesDiv.querySelector('.remove-series');
554
+ removeBtn.addEventListener('click', () => seriesDiv.remove());
555
+ }
556
+
557
+ function saveWorkout() {
558
+ if (!currentUser) return;
559
+
560
+ // --- Récupération Données ---
561
+ const workoutName = document.getElementById('workout-name').value.trim();
562
+ const workoutDate = document.getElementById('workout-date').value;
563
+ const workoutDuration = parseInt(document.getElementById('workout-duration').value) || 0;
564
+ const satisfaction = parseInt(document.getElementById('satisfaction').value);
565
+
566
+ // --- Validation Données Séance ---
567
+ if (!workoutName) { alert("Veuillez entrer un nom pour la séance."); return; }
568
+ if (!workoutDate) { alert("Veuillez choisir une date pour la séance."); return; }
569
+ if (workoutDuration <= 0) { alert("Veuillez entrer une durée valide (en minutes)."); return; }
570
+
571
+ // --- Récupération Exercices ---
572
+ const exercises = [];
573
+ const exerciseElements = exercisesContainer.querySelectorAll('.exercise');
574
+
575
+ if (exerciseElements.length === 0) { alert("Veuillez ajouter au moins un exercice à la séance."); return; }
576
+
577
+ let validationError = null; // Pour stocker le message d'erreur
578
+
579
+ exerciseElements.forEach(exerciseEl => {
580
+ if (validationError) return; // Arrêter si une erreur précédente a eu lieu
581
+
582
+ const exerciseName = exerciseEl.querySelector('.exercise-name').value.trim();
583
+ const isUnilateral = exerciseEl.querySelector('.unilateral-checkbox').checked;
584
+
585
+ if (!exerciseName) {
586
+ validationError = "Veuillez nommer tous les exercices.";
587
+ return;
588
+ }
589
+
590
+ const series = [];
591
+ const seriesElements = exerciseEl.querySelectorAll('.series');
592
+
593
+ if (seriesElements.length === 0) {
594
+ validationError = `L'exercice "${exerciseName}" doit contenir au moins une série.`;
595
+ return;
596
+ }
597
+
598
+ seriesElements.forEach(seriesEl => {
599
+ if (validationError) return;
600
+
601
+ const repsInput = seriesEl.querySelector('.reps');
602
+ const weightInput = seriesEl.querySelector('.weight');
603
+ const reps = parseInt(repsInput.value) || 0;
604
+ const weight = parseFloat(weightInput.value) || 0; // Permet 0 pour poids du corps
605
+ const isDegressive = seriesEl.querySelector('.degressive-checkbox').checked;
606
+
607
+ if (reps <= 0) {
608
+ validationError = `Nombre de répétitions invalide pour une série de "${exerciseName}".`;
609
+ repsInput.focus(); // Focus sur le champ erroné
610
+ return;
611
+ }
612
+ if (weight < 0) { // Poids négatif n'est pas permis
613
+ validationError = `Charge invalide pour une série de "${exerciseName}".`;
614
+ weightInput.focus();
615
+ return;
616
+ }
617
+ series.push({ reps, weight, isDegressive });
618
+ });
619
+
620
+ if (!validationError) {
621
+ exercises.push({ name: exerciseName, isUnilateral, series });
622
+ }
623
+ });
624
+
625
+ // Afficher l'erreur et arrêter si nécessaire
626
+ if (validationError) {
627
+ alert(validationError);
628
+ return;
629
+ }
630
+
631
+ // --- Calcul Tonnage ---
632
+ let totalTonnage = 0;
633
+ exercises.forEach(exercise => {
634
+ exercise.series.forEach(serie => {
635
+ const weightFactor = exercise.isUnilateral ? 2 : 1; // Compte double pour unilatéral
636
+ totalTonnage += serie.reps * serie.weight * weightFactor;
637
+ });
638
+ });
639
+
640
+ // --- Création Objet Séance ---
641
+ const workout = {
642
+ id: currentWorkoutId || `workout-${Date.now()}`, // Utilise l'ID existant si modification
643
+ name: workoutName,
644
+ date: workoutDate,
645
+ duration: workoutDuration,
646
+ exercises: exercises, // Utilise la variable locale remplie
647
+ totalTonnage: totalTonnage,
648
+ satisfaction: satisfaction
649
+ };
650
+
651
+ // --- Ajout ou Mise à Jour ---
652
+ const existingIndex = workouts.findIndex(w => w.id === workout.id);
653
+ if (existingIndex > -1) {
654
+ workouts[existingIndex] = workout; // Mise à jour
655
+ console.log("Séance mise à jour:", workout.id);
656
+ } else {
657
+ workouts.push(workout); // Ajout nouvelle séance
658
+ console.log("Nouvelle séance ajoutée:", workout.id);
659
+ }
660
+
661
+ // --- Sauvegarde et Affichage ---
662
+ saveWorkouts();
663
+ showPage('home-page');
664
+ renderWorkoutsList(); // Mettre à jour la liste immédiatement
665
+ }
666
+
667
+ function renderWorkoutsList() {
668
+ if (!currentUser) return; // Sécurité
669
+ workoutsList.innerHTML = ''; // Vider avant de remplir
670
+
671
+ if (workouts.length === 0) {
672
+ emptyWorkoutMessage.classList.remove('hidden'); // Afficher message si vide
673
+ updateStats(); // Mettre à jour les stats (qui seront à 0)
674
+ return;
675
+ }
676
+
677
+ emptyWorkoutMessage.classList.add('hidden'); // Cacher message si pas vide
678
+
679
+ // Trier par date (plus récent en premier)
680
+ const sortedWorkouts = [...workouts].sort((a, b) => new Date(b.date) - new Date(a.date));
681
+
682
+ sortedWorkouts.forEach(workout => {
683
+ const workoutDate = new Date(workout.date).toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric' });
684
+ const workoutDiv = document.createElement('div');
685
+ workoutDiv.className = 'card workout-card';
686
+ workoutDiv.setAttribute('data-workout-id', workout.id);
687
+
688
+ // Simplification de l'affichage pour plus de clarté
689
+ workoutDiv.innerHTML = `
690
+ <div class="workout-header">
691
+ <h3 style="margin-bottom: 0.5rem;">${workout.name}</h3>
692
+ <div class="badge">${workoutDate}</div>
693
+ </div>
694
+ <div class="workout-details" style="font-size: 0.9rem; color: #ccc; margin-top: 0.5rem;">
695
+ <span>${workout.duration} min</span> |
696
+ <span>${workout.exercises.length} exo${workout.exercises.length > 1 ? 's' : ''}</span> |
697
+ <span>${workout.totalTonnage.toFixed(1)} kg</span> |
698
+ <span>${workout.satisfaction}%</span>
699
+ </div>
700
+ `;
701
+ workoutDiv.addEventListener('click', () => displayWorkoutDetails(workout.id));
702
+ workoutsList.appendChild(workoutDiv);
703
+ });
704
+
705
+ updateStats(); // Mettre à jour les stats globales
706
+ }
707
+
708
+ function displayWorkoutDetails(workoutId) {
709
+ if (!currentUser) return;
710
+ const workout = workouts.find(w => w.id === workoutId);
711
+ if (!workout) {
712
+ console.error("Séance non trouvée:", workoutId);
713
+ showPage('home-page'); // Retour à l'accueil si non trouvée
714
+ return;
715
+ }
716
+
717
+ // MAJ Infos générales
718
+ document.getElementById('detail-workout-name').textContent = workout.name;
719
+ document.getElementById('detail-date').textContent = new Date(workout.date).toLocaleDateString('fr-FR');
720
+ document.getElementById('detail-duration').textContent = workout.duration;
721
+ document.getElementById('detail-tonnage').textContent = workout.totalTonnage.toFixed(1);
722
+ document.getElementById('detail-satisfaction').textContent = `${workout.satisfaction}%`;
723
+ document.getElementById('detail-exercises-count').textContent = workout.exercises.length;
724
+
725
+ // MAJ Liste des exercices détaillés
726
+ const detailExercisesContainer = document.getElementById('detail-exercises-container');
727
+ detailExercisesContainer.innerHTML = ''; // Vider
728
+
729
+ workout.exercises.forEach(exercise => {
730
+ const exerciseDiv = document.createElement('div');
731
+ exerciseDiv.className = 'card'; // Style carte pour chaque exo
732
+ let seriesHtml = '';
733
+ exercise.series.forEach((serie, index) => {
734
+ const degressiveLabel = serie.isDegressive ? ' <span class="badge" style="font-size: 0.7rem; background-color: var(--accent-dark);">Dégr.</span>' : '';
735
+ const weightFactor = exercise.isUnilateral ? 2 : 1;
736
+ const seriesTonnage = serie.reps * serie.weight * weightFactor;
737
+ seriesHtml += `
738
+ <div class="exercise-summary">
739
+ <div class="flex-between">
740
+ <span>Série ${index + 1}: ${serie.reps} reps × ${serie.weight} kg${degressiveLabel}</span>
741
+ <span style="color: #aaa;">(${seriesTonnage.toFixed(1)} kg)</span>
742
+ </div>
743
+ </div>
744
+ `;
745
+ });
746
+
747
+ const unilateralLabel = exercise.isUnilateral ? ' <span class="badge" style="font-size: 0.7rem;">Unilat.</span>' : '';
748
+ exerciseDiv.innerHTML = `
749
+ <h3 style="font-size: 1.1rem; margin-bottom: 0.5rem;">${exercise.name}${unilateralLabel}</h3>
750
+ <div class="series-summary-container">
751
+ ${seriesHtml}
752
+ </div>`;
753
+ detailExercisesContainer.appendChild(exerciseDiv);
754
+ });
755
+
756
+ currentWorkoutId = workoutId; // Stocker pour suppression éventuelle
757
+ showPage('workout-details-page'); // Afficher la page détail
758
+ }
759
+
760
+ function deleteWorkout() {
761
+ if (!currentUser || !currentWorkoutId) return;
762
+ const workoutToDelete = workouts.find(w => w.id === currentWorkoutId);
763
+ if (!workoutToDelete) return; // Sécurité
764
+
765
+ const confirmDelete = confirm(`Êtes-vous sûr de vouloir supprimer la séance "${workoutToDelete.name}" du ${new Date(workoutToDelete.date).toLocaleDateString('fr-FR')} ?\nCette action est irréversible.`);
766
+ if (!confirmDelete) return;
767
+
768
+ // Filtrer la séance
769
+ workouts = workouts.filter(w => w.id !== currentWorkoutId);
770
+ saveWorkouts(); // Sauvegarder la liste mise à jour
771
+ currentWorkoutId = null; // Réinitialiser l'ID courant
772
+ showPage('home-page');
773
+ renderWorkoutsList(); // Rafraîchir la liste
774
+ }
775
+
776
+ function clearNewWorkoutForm() {
777
+ if (!currentUser) return;
778
+ document.getElementById('workout-name').value = '';
779
+ document.getElementById('workout-duration').value = '';
780
+ setTodayDate(); // Remettre date du jour
781
+ exercisesContainer.innerHTML = ''; // Vider les exercices ajoutés
782
+ satisfactionRange.value = 75; // Reset satisfaction
783
+ satisfactionValue.textContent = '75%';
784
+ currentWorkoutId = null; // Assurer qu'on est bien en mode création
785
+ }
786
+
787
+ function updateStats() {
788
+ if (!currentUser) return;
789
+ const workoutCount = workouts.length;
790
+
791
+ document.getElementById('stats-workout-count').textContent = workoutCount;
792
+
793
+ if (workoutCount === 0) {
794
+ document.getElementById('stats-avg-tonnage').textContent = '0';
795
+ document.getElementById('stats-avg-satisfaction').textContent = '0%';
796
+ return;
797
+ }
798
+
799
+ const totalTonnageAll = workouts.reduce((sum, workout) => sum + (workout.totalTonnage || 0), 0);
800
+ const avgTonnage = totalTonnageAll / workoutCount;
801
+ document.getElementById('stats-avg-tonnage').textContent = avgTonnage.toFixed(1);
802
+
803
+ const totalSatisfaction = workouts.reduce((sum, workout) => sum + (workout.satisfaction || 0), 0);
804
+ const avgSatisfaction = totalSatisfaction / workoutCount;
805
+ document.getElementById('stats-avg-satisfaction').textContent = `${Math.round(avgSatisfaction)}%`;
806
+ }
807
+
808
+ </script>
809
+ </body>
810
+ </html>