From cc580b10120ac0c06a3e603b541d7c6e8329ec32 Mon Sep 17 00:00:00 2001 From: fioreale Date: Sat, 16 Sep 2023 12:28:34 +0200 Subject: [PATCH 01/13] fixed timer stop behavior --- app/public/index.html | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/public/index.html b/app/public/index.html index a7a27cc..d6dd1da 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -586,13 +586,14 @@
Nuovo Workout
} stop() { - this.clearInterval(); + clearInterval(this.intervalId); this.updateDisplay(); } reset() { - this.clearInterval(); - this.start(0); + clearInterval(this.intervalId); + this.endTime = Date.now(); + this.tick(); } tick() { @@ -612,12 +613,6 @@
Nuovo Workout
seconds < 10 ? "0" : "" }${seconds}`; } - - clearInterval() { - if (this.intervalId) { - this.intervalId = null; - } - } } const display = document.querySelector("#time"); From 1913983afdd84fa01c841228e3ddb25af9c57285 Mon Sep 17 00:00:00 2001 From: fioreale Date: Tue, 20 Feb 2024 22:25:58 +0100 Subject: [PATCH 02/13] no repetited exercises --- app/api/models/workout.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/api/models/workout.py b/app/api/models/workout.py index 5ac369e..a4429a0 100644 --- a/app/api/models/workout.py +++ b/app/api/models/workout.py @@ -4,13 +4,14 @@ class Serie(BaseModel): + series: int reps: str carico: str class Esercizio(BaseModel): name: str - serie: List[Serie] + serie: Serie class Scheda(BaseModel): From 0130a9e37873cb2c3f6e1349d806a57a48e6c0bc Mon Sep 17 00:00:00 2001 From: fioreale Date: Tue, 20 Feb 2024 22:26:28 +0100 Subject: [PATCH 03/13] compose update --- docker-compose.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e8c6731..f2e84b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,16 @@ -version: '3.9' +version: "3.9" services: - myFitApp: ports: - "8000:8000" image: my-fitapp container_name: my-fitapp - build: + build: context: . dockerfile: ./docker/Dockerfile restart: on-failure + networks: + - ferretdb postgres: image: postgres:alpine @@ -20,6 +21,10 @@ services: - POSTGRES_USER=username - POSTGRES_PASSWORD=password - POSTGRES_DB=ferretdb + networks: + ferretdb: + aliases: + - postgres.internal ferretdb: image: ghcr.io/ferretdb/ferretdb:latest @@ -29,7 +34,11 @@ services: - 27017:27017 environment: - FERRETDB_POSTGRESQL_URL=postgres://postgres:5432/ferretdb + networks: + ferretdb: + aliases: + - ferretdb.internal networks: - default: - name: ferretdb + ferretdb: + driver: bridge From 44d131e2d6d8dc13375065e9c9455752ce80e36e Mon Sep 17 00:00:00 2001 From: fioreale Date: Thu, 22 Feb 2024 00:17:59 +0100 Subject: [PATCH 04/13] improved ui/ux + under the hood performance --- app/public/assets/JS/changeWorkout.js | 12 +- app/public/assets/JS/fillPage.js | 255 +++++++++++++++----------- app/public/assets/JS/modifySerie.js | 71 ++++--- app/public/assets/JS/sendWorkout.js | 15 +- app/public/assets/JS/timer.js | 46 ++--- app/public/assets/JS/updateScheda.js | 111 ++++++----- 6 files changed, 266 insertions(+), 244 deletions(-) diff --git a/app/public/assets/JS/changeWorkout.js b/app/public/assets/JS/changeWorkout.js index 6c02183..296731d 100644 --- a/app/public/assets/JS/changeWorkout.js +++ b/app/public/assets/JS/changeWorkout.js @@ -1,11 +1,11 @@ -function changeWorkout() { - var list_workouts = document.querySelectorAll(".listWorkouts"); +const workoutsContainer = document.querySelector(".listWorkouts"); - list_workouts.forEach((workout_button) => { - workout_button.onclick = (event) => { - // from FillPage.js +if (workoutsContainer) { + workoutsContainer.addEventListener("click", (event) => { + // Ensure the clicked element is one of the workout buttons + if (event.target.matches(".workoutButton")) { // Assuming `.workoutButton` class exists on each button fillPage(event.target.textContent); $("#modalAddWorkout").modal("hide"); - }; + } }); } diff --git a/app/public/assets/JS/fillPage.js b/app/public/assets/JS/fillPage.js index c68deb2..fee9536 100644 --- a/app/public/assets/JS/fillPage.js +++ b/app/public/assets/JS/fillPage.js @@ -1,131 +1,178 @@ -var list = document.querySelector(".listExercises"); -list.innerHTML = ""; -var schedaButtons = document.querySelector("#schedeButtons"); -while (schedaButtons.previousElementSibling !== null) { - schedaButtons.previousElementSibling.remove(); +// Use const for elements that do not change +const list = document.querySelector(".listExercises"); +const schedaButtons = document.querySelector("#schedeButtons"); + +// Refactor repetitive DOM manipulation into functions +function clearElement(element) { + element.innerHTML = ""; } -fillListWorkouts(); -function fillPage(workout_id) { - list.innerHTML = ""; - while (schedaButtons.previousElementSibling !== null) { - schedaButtons.previousElementSibling.remove(); +function removeAllPreviousSiblings(element) { + while (element.previousElementSibling !== null) { + element.previousElementSibling.remove(); } +} + +// Initial cleanup +clearElement(list); +removeAllPreviousSiblings(schedaButtons); +fillListWorkouts(); + +// Refactored to use async/await for readability +async function fillPage(workoutId) { + clearElement(list); + removeAllPreviousSiblings(schedaButtons); + + // Simulate delay if needed + await new Promise((resolve) => setTimeout(resolve, 1000)); - setTimeout(() => { - fetch("../workout/" + workout_id, { - method: "GET", - }) - .then(function (response) { - return response.json(); - }) - .then(function (json) { - fillButtons(json); - - let arraySchede = document.querySelectorAll(".schedaButton"); - - arraySchede.forEach((el) => { - el.addEventListener("click", (event) => { - list.innerHTML = ""; - let schedaName = event.target.textContent; - fillScheda(json, schedaName); - // ─── From Modifyserie.js ───── - modifySerie(); - updateScheda(workout_id); - sendUpdate.parentElement.removeAttribute("hidden"); - // ───────────────────────────── - }); - }); + try { + const response = await fetch(`../workout/${workoutId}`); + const json = await response.json(); + fillButtons(json); + + document.querySelectorAll(".schedaButton").forEach((button) => { + button.addEventListener("click", async (event) => { + clearElement(list); + const schedaName = event.target.textContent; + fillScheda(json, schedaName); + + modifySerie(); + updateScheda(workoutId); + // sendUpdate.parentElement.removeAttribute("hidden"); }); - }, 1000); + }); + } catch (error) { + console.error("Failed to fetch workout details:", error); + } } function fillScheda(workout, name) { list.setAttribute("scheda", name); - for (let i = 0; i < workout.schede.length; i++) { - if (workout.schede[i].name === name) { - workout.schede[i].esercizi.forEach((el) => { - let elJSON = fillExercise(el.name, el.serie); - list.appendChild(elJSON); - }); - } - } + workout.schede + .find((s) => s.name === name) + ?.esercizi.forEach((exercise) => { + const exerciseElement = createExerciseElement( + exercise.name, + exercise.serie + ); + list.appendChild(exerciseElement); + }); } -function fillExercise(name, serie) { - let li = document.createElement("li"); - let h5 = document.createElement("h5"); - h5.textContent = name; - - li.setAttribute("class", "list-group-item esercizi"); - li.appendChild(h5); - for (let i = 0; i < serie.length; i++) { - li.innerHTML += completeElement( - name, - i + 1, - serie[i].carico, - serie[i].reps - ); - } - +function createExerciseElement(name, serie) { + const li = document.createElement("li"); + li.className = "list-group-item esercizi"; + li.innerHTML = + `
${name}
` + + completeElement(name, serie.series, serie.carico, serie.reps); return li; } -function completeElement(name, num, carico, reps) { - let nameToPlace = name.replace(/[^a-zA-Z0-9]+/g, ""); - let exElement = `
\ -
\ -
\ -
`; +// Refactor to use template literals and extract similar functionality +function completeElement(name, series_num, carico, reps) { + const nameToPlace = name.replace(/[^a-zA-Z0-9]+/g, ""); + const increment = 100 / series_num; // Calculate increment per series completion + + // Button to mark a series as completed, with an onclick event calling incrementSeries function + const buttonHTML = ` + + `; + + // Exercise details with badges and update button + const exerciseDetailsHTML = ` +
+ ${buttonHTML} +
+ ${series_num} + ${carico} + ${reps} +
+ +
+ `; + + // Progress bar HTML, placed in its own 'row' under the exercise details + const progressBarHTML = ` +
+
+
+ `; + + // Combine exercise details and progress bar + return ` +
+ ${exerciseDetailsHTML} + ${progressBarHTML} +
+ `; +} + +// Function to increment series progress +function incrementSeries(nameToPlace, increment) { + const progressBar = document.getElementById(`progress${nameToPlace}`); + let currentProgress = parseFloat(progressBar.style.width) || 0; + let newProgress = currentProgress + increment; + newProgress = newProgress > 100 ? 100 : newProgress; // Ensure progress does not exceed 100% - return exElement; + progressBar.style.width = `${newProgress}%`; + progressBar.setAttribute("aria-valuenow", newProgress); + + // Optional: Perform additional actions when series is completed + if (newProgress === 100) { + // Example: Alert completion or disable button + console.log("Series completed for", nameToPlace); + } } +// ─── Populate Series Buttons ───────────────────────────────────────────── ✣ ─ function fillButtons(json) { - // ─── Create Button Group ───────────────────────────────────────────── - let new_buttons = document.createElement("div"); - new_buttons.className = "p-2"; - let new_btn_group = document.createElement("div"); - new_btn_group.className = "btn-group"; - new_btn_group.role = "group"; - - // ─── Add Buttons ───────────────────────────────────────────────────── + const buttonContainer = document.createElement("div"); + buttonContainer.className = "btn-group"; + buttonContainer.setAttribute("role", "group"); + + // Use a document fragment to minimize DOM manipulation + const fragment = document.createDocumentFragment(); + json.schede.forEach((scheda) => { - let name = scheda.name; - let button = ``; - new_btn_group.innerHTML += button; + const button = document.createElement("button"); + button.type = "button"; + button.className = "btn btn-secondary schedaButton"; + button.textContent = scheda.name; + fragment.appendChild(button); }); - new_buttons.insertAdjacentElement("afterbegin", new_btn_group); - - // ─── Create Delete Button ──────────────────────────────────────────── - let delete_button = document.createElement("div"); - delete_button.className = "p-2"; - delete_button.innerHTML = ``; - schedaButtons.insertAdjacentElement("beforebegin", new_buttons); - new_buttons.insertAdjacentElement("beforebegin", delete_button); + buttonContainer.appendChild(fragment); + schedaButtons.insertAdjacentElement("beforebegin", buttonContainer); // ─── Create Listener For Workout Deletion ──────────────────────────── - setDeleteWorkout(); + // setDeleteWorkout(); } -function fillListWorkouts() { - let list_workouts = document.querySelector(".listWorkouts"); - list_workouts.innerHTML = ""; - - fetch("../workout") - .then(function (response) { - return response.json(); - }) - .then(function (array) { - array.forEach((el) => { - let button = ``; - list_workouts.innerHTML += button; - }); - // ─── From Changeworkout.js ─────────────────────────── - changeWorkout(); - // ───────────────────────────────────────────────────── +// ─── Populate Workouts Buttons ─────────────────────────────────────────── ✣ ─ +async function fillListWorkouts() { + const listWorkouts = document.querySelector(".listWorkouts"); + clearElement(listWorkouts); // Reusing the clearElement function from the previous refactor + + try { + const response = await fetch("../workout"); + const workouts = await response.json(); + + // Similar to fillButtons, use a document fragment for efficiency + const fragment = document.createDocumentFragment(); + + workouts.forEach((workout) => { + const button = document.createElement("button"); + button.type = "button"; + button.className = + "btn-sm list-group-item list-group-item-action workoutButton"; + button.textContent = workout; + fragment.appendChild(button); }); + + listWorkouts.appendChild(fragment); + + } catch (error) { + console.error("Failed to load workouts:", error); + } } diff --git a/app/public/assets/JS/modifySerie.js b/app/public/assets/JS/modifySerie.js index 49f9597..27f91d3 100644 --- a/app/public/assets/JS/modifySerie.js +++ b/app/public/assets/JS/modifySerie.js @@ -1,47 +1,42 @@ function modifySerie() { - var elements = document.querySelectorAll(".modalMOD"); - var save_changes = document.querySelector(".saveChanges"); - var weightMOD = null; - var repsMOD = null; + const elements = document.querySelectorAll(".modalMOD"); + const saveChanges = document.querySelector(".saveChanges"); - let weight = document.querySelector(".inputWeight"); - let reps = document.querySelector(".inputReps"); + let weightMOD, repsMOD; + + // Reference to the input fields in the modal + const weightInput = document.querySelector(".inputWeight"); + const repsInput = document.querySelector(".inputReps"); elements.forEach((el) => { - el.onclick = (event) => { - weightMOD = - event.target.parentElement.parentElement.firstElementChild - .firstElementChild.firstElementChild.nextElementSibling - .firstElementChild; + el.addEventListener("click", (event) => { + // Navigate the DOM to find the corresponding weight and reps badges + const exerciseDetail = event.target + .closest(".d-flex.align-items-center") + .querySelector(".exercise-details"); + weightMOD = exerciseDetail.querySelector(".weight"); + repsMOD = exerciseDetail.querySelector(".reps"); + + // Set input placeholders to current values + weightInput.placeholder = weightMOD.textContent; + repsInput.placeholder = repsMOD.textContent; + + // Optionally, set input values to current values for direct editing + weightInput.value = weightMOD.textContent; + repsInput.value = repsMOD.textContent; + }); + }); - repsMOD = weightMOD.nextElementSibling; + saveChanges.addEventListener("click", () => { + // Update textContent based on input values or revert to placeholders if inputs are empty + weightMOD.textContent = weightInput.value || weightInput.placeholder; + repsMOD.textContent = repsInput.value || repsInput.placeholder; - weight.placeholder = weightMOD.textContent; - reps.placeholder = repsMOD.textContent; - }; - }); + // Reset input fields + weightInput.value = ""; + repsInput.value = ""; - // ------------------------------------------------------------- - - save_changes.onclick = function () { - if (weight.value.length === 0 && reps.value.length > 0) { - weightMOD.textContent = weight.placeholder; - repsMOD.textContent = reps.value; - } else if (reps.value.length === 0 && weight.value.length > 0) { - weightMOD.textContent = weight.value; - repsMOD.textContent = reps.placeholder; - } else if (reps.value.length > 0 && weight.value.length > 0) { - weightMOD.textContent = weight.value; - repsMOD.textContent = reps.value; - } else { - weightMOD.textContent = weight.placeholder; - repsMOD.textContent = reps.placeholder; - } - - weight.value = ""; - reps.value = ""; - - // close the modal window + // Close the modal window using jQuery $("#modalModifySerie").modal("hide"); - }; + }); } diff --git a/app/public/assets/JS/sendWorkout.js b/app/public/assets/JS/sendWorkout.js index 624c47d..93c9907 100644 --- a/app/public/assets/JS/sendWorkout.js +++ b/app/public/assets/JS/sendWorkout.js @@ -139,17 +139,12 @@ function createEx_JSON(exerciseRAW) { let esercizio = { name: titleEx, - serie: [], + serie: { + series: howMany, + reps: reps, + carico: "0", + }, }; - let serie = { - reps: reps, - carico: "0", - }; - - for (let i = 0; i < howMany; i++) { - esercizio.serie.push(serie); - } - return esercizio; } diff --git a/app/public/assets/JS/timer.js b/app/public/assets/JS/timer.js index aab026c..cb56a59 100644 --- a/app/public/assets/JS/timer.js +++ b/app/public/assets/JS/timer.js @@ -1,34 +1,22 @@ -document.querySelector("#option1").addEventListener("click", () => { - setTime(5); +// Assuming all option buttons share a common class 'time-option' +document.querySelectorAll(".time-option").forEach((button) => { + button.addEventListener("click", () => { + const secStep = parseInt(button.dataset.time, 10); // Use data attributes e.g., data-time="5" + setTime(secStep); + }); }); -document.querySelector("#option2").addEventListener("click", () => { - setTime(10); -}); -document.querySelector("#option3").addEventListener("click", () => { - setTime(15); -}); -document.querySelector("#option4").addEventListener("click", () => { - setTime(30); -}); -document.querySelector("#option5").addEventListener("click", () => { - setTime(60); -}); - -function setTime(sec_step) { - minutes = parseInt( - document.querySelector("#rest-time").innerText.split(":")[0] - ); - seconds = parseInt( - document.querySelector("#rest-time").innerText.split(":")[1] - ); - time = seconds + minutes * 60; - - time += sec_step; - minutes = parseInt(time / 60, 10); - seconds = parseInt(time % 60, 10); +function setTime(secStep) { + const restTimeElement = document.querySelector("#rest-time"); + let [minutes, seconds] = restTimeElement.textContent + .split(":") + .map((num) => parseInt(num, 10)); + let totalTime = minutes * 60 + seconds + secStep; - seconds = seconds < 10 ? "0" + seconds : seconds; + minutes = parseInt(totalTime / 60, 10); + seconds = totalTime % 60; - document.querySelector("#rest-time").innerText = minutes + ":" + seconds; + restTimeElement.textContent = `${minutes}:${ + seconds < 10 ? "0" : "" + }${seconds}`; } diff --git a/app/public/assets/JS/updateScheda.js b/app/public/assets/JS/updateScheda.js index 529c392..5fe3739 100644 --- a/app/public/assets/JS/updateScheda.js +++ b/app/public/assets/JS/updateScheda.js @@ -1,65 +1,62 @@ -let sendUpdate = document.querySelector("#updateScheda"); -sendUpdate.parentElement.setAttribute("hidden", ""); - -function updateScheda(workout_id) { - sendUpdate.onclick = function () { - let exercisesArray = document.querySelectorAll(".esercizi"); - let scheda_name = document - .querySelector(".listExercises") - .getAttribute("scheda"); - - if (scheda_name !== "") { - let Scheda = { - name: scheda_name, - esercizi: [], +async function updateScheda(workoutId) { + const sendUpdate = document.querySelector("#updateScheda"); + sendUpdate.addEventListener("click", async () => { + const exercisesList = document.querySelectorAll(".esercizi"); + const schedaName = document.querySelector(".listExercises").getAttribute("scheda"); + + if (schedaName) { + const updatedScheda = { + name: schedaName, + esercizi: Array.from(exercisesList).map((el) => { + const exerciseName = el.querySelector("h5").textContent; + const weightBadge = el.querySelector(".weight").textContent; + const repsBadge = el.querySelector(".reps").textContent; + const seriesBadge = el.querySelector(".series").textContent; + + return { + name: exerciseName, + serie: { + series: parseInt(seriesBadge, 10), // Ensure series is an integer + carico: weightBadge, + reps: repsBadge, + }, + }; + }), }; - exercisesArray.forEach((el) => { - let exerciseName = el.firstElementChild.textContent; - - let exercise = { - name: exerciseName, - serie: [], - }; - - let arraySerie = document.querySelectorAll( - "." + exerciseName.replace(/[^a-zA-Z0-9]+/g, "") + "Serie" - ); - - arraySerie.forEach((serie) => { - let carico = serie.firstElementChild.textContent; - let reps = serie.firstElementChild.nextElementSibling.textContent; + console.log(JSON.stringify(updatedScheda)); // Debugging line to verify data structure - exercise.serie.push({ - reps: reps, - carico: carico, - }); + try { + const response = await fetch(`../workout/${workoutId}`, { + method: "PATCH", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(updatedScheda), }); - Scheda.esercizi.push(exercise); - }); + if (!response.ok) { + throw new Error(`Failed to update workout: ${response.statusText}`); + } - fetch("../workout/" + workout_id, { - method: "PATCH", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify(Scheda), - }) - .then(function (response) { - // from fillPage.js - fillPage(workout_id); - sendUpdate.parentElement.setAttribute("hidden", ""); - document.querySelector("#alert-text").textContent = - "Aggiornamento eseguito!"; - $("#alert-modal").modal("show"); - }) - .catch(function (error) { - document.querySelector("#alert-text").textContent = - "Impossibile inserire workout!"; - $("#alert-modal").modal("show"); - }); + fillPage(workoutId); + showAlert("Aggiornamento eseguito!", "success"); + } catch (error) { + console.error(error); // Log the error for debugging + showAlert("Impossibile aggiornare il workout!", "danger"); + } } - }; + }); +} + + +function showAlert(message, type) { + const alertText = document.querySelector("#alert-text"); + alertText.textContent = message; + // Use Bootstrap's modal and alert classes to show the message + // Adjust according to your modal setup + const alertModal = $("#alert-modal"); + alertModal.find(".modal-body").className = `modal-body text-${type}`; + alertModal.modal("show"); } From 63d6151e892e86bee1700cebcb543af2a43bc4c7 Mon Sep 17 00:00:00 2001 From: fioreale Date: Thu, 22 Feb 2024 00:18:36 +0100 Subject: [PATCH 05/13] index optimizations for better ui/ux --- app/public/index.html | 165 +++++++++++++++--------------------------- 1 file changed, 57 insertions(+), 108 deletions(-) diff --git a/app/public/index.html b/app/public/index.html index d6dd1da..76566e4 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -7,11 +7,13 @@ content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0" /> + + @@ -19,7 +21,7 @@ @@ -88,19 +90,44 @@ >
- - - - -