From d3e412604b9eafc96317208f72df204c8046adbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vit=C3=B3ria=20Silva?= Date: Fri, 31 Oct 2025 11:56:11 +0000 Subject: [PATCH] Add treadmill sport type & bump version --- backend/app/activities/activity/utils.py | 4 ++- backend/app/core/config.py | 2 +- backend/app/users/user_goals/utils.py | 27 ++++++++++++------- backend/pyproject.toml | 2 +- docs/developer-guide.md | 1 + frontend/app/package-lock.json | 4 +-- frontend/app/package.json | 2 +- .../Modals/EditActivityModalComponent.vue | 3 +++ .../app/src/components/FooterComponent.vue | 2 +- frontend/app/src/i18n/us/activityItems.json | 3 ++- .../modals/editActivityModalComponent.json | 3 ++- frontend/app/src/utils/activityUtils.js | 16 ++++++----- frontend/app/src/views/SearchView.vue | 2 +- 13 files changed, 45 insertions(+), 26 deletions(-) diff --git a/backend/app/activities/activity/utils.py b/backend/app/activities/activity/utils.py index d68fe8285..df719e297 100644 --- a/backend/app/activities/activity/utils.py +++ b/backend/app/activities/activity/utils.py @@ -86,6 +86,7 @@ ACTIVITY_ID_TO_NAME = { 37: "Ice Skate", 38: "Soccer", 39: "Padel", + 40: "Treadmill", # Add other mappings as needed based on the full list in define_activity_type comments if required # "AlpineSki", # "BackcountrySki", @@ -211,6 +212,7 @@ ACTIVITY_NAME_TO_ID.update( "padel": 39, "padelball": 39, "paddelball": 39, + "treadmill": 40, } ) @@ -797,7 +799,7 @@ def calculate_activity_distances(activities: list[activities_schema.Activity]): if activities is not None: # Calculate the distances for activity in activities: - if activity.activity_type in [1, 2, 3, 34]: + if activity.activity_type in [1, 2, 3, 34, 40]: run += activity.distance elif activity.activity_type in [4, 5, 6, 7, 27, 28, 29, 35, 36]: bike += activity.distance diff --git a/backend/app/core/config.py b/backend/app/core/config.py index dfdc6ceef..845ba8b13 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -7,7 +7,7 @@ from cryptography.fernet import Fernet import core.logger as core_logger # Constant related to version -API_VERSION = "v0.15.4" +API_VERSION = "v0.15.5" LICENSE_NAME = "GNU Affero General Public License v3.0 or later" LICENSE_IDENTIFIER = "AGPL-3.0-or-later" LICENSE_URL = "https://spdx.org/licenses/AGPL-3.0-or-later.html" diff --git a/backend/app/users/user_goals/utils.py b/backend/app/users/user_goals/utils.py index aff520747..7f896b0df 100644 --- a/backend/app/users/user_goals/utils.py +++ b/backend/app/users/user_goals/utils.py @@ -40,8 +40,7 @@ def calculate_user_goals( return None return [ - calculate_goal_progress_by_activity_type(goal, date, db) - for goal in goals + calculate_goal_progress_by_activity_type(goal, date, db) for goal in goals ] except HTTPException as http_err: raise http_err @@ -76,19 +75,19 @@ def calculate_goal_progress_by_activity_type( user_goals_schema.UserGoalProgress | None: An object containing progress details for the goal, or None if no activities are found. Raises: HTTPException: If an error occurs during processing or database access. - """ + """ try: start_date, end_date = get_start_end_date_by_interval(goal.interval, date) # Define activity type mappings TYPE_MAP = { - user_goals_schema.ActivityType.RUN: [1, 2, 3, 34], + user_goals_schema.ActivityType.RUN: [1, 2, 3, 34, 40], user_goals_schema.ActivityType.BIKE: [4, 5, 6, 7, 27, 28, 29, 35, 36], user_goals_schema.ActivityType.SWIM: [8, 9], user_goals_schema.ActivityType.WALK: [11, 12], } DEFAULT_TYPES = (19, 20) - + # Get activity types based on goal.activity_type, default to [10, 19, 20] activity_types = TYPE_MAP.get(goal.activity_type, DEFAULT_TYPES) @@ -96,7 +95,7 @@ def calculate_goal_progress_by_activity_type( activities = activity_crud.get_user_activities_per_timeframe_and_activity_types( goal.user_id, activity_types, start_date, end_date, db, True ) - + # Calculate totals based on goal type percentage_completed = 0 total_calories = 0 @@ -113,14 +112,20 @@ def calculate_goal_progress_by_activity_type( total_distance = sum(activity.distance or 0 for activity in activities) percentage_completed = (total_distance / goal.goal_distance) * 100 elif goal.goal_type == user_goals_schema.GoalType.ELEVATION: - total_elevation = sum(activity.elevation_gain or 0 for activity in activities) + total_elevation = sum( + activity.elevation_gain or 0 for activity in activities + ) percentage_completed = (total_elevation / goal.goal_elevation) * 100 elif goal.goal_type == user_goals_schema.GoalType.DURATION: - total_duration = sum(activity.total_elapsed_time or 0 for activity in activities) + total_duration = sum( + activity.total_elapsed_time or 0 for activity in activities + ) percentage_completed = (total_duration / goal.goal_duration) * 100 elif goal.goal_type == user_goals_schema.GoalType.ACTIVITIES: total_activities_number = len(activities) - percentage_completed = (total_activities_number / goal.goal_activities_number) * 100 + percentage_completed = ( + total_activities_number / goal.goal_activities_number + ) * 100 if percentage_completed > 100: percentage_completed = 100 @@ -208,7 +213,9 @@ def get_start_end_date_by_interval( """ date_obj = datetime.strptime(date, "%Y-%m-%d") if interval == "yearly": - start_date = date_obj.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0) + start_date = date_obj.replace( + month=1, day=1, hour=0, minute=0, second=0, microsecond=0 + ) # Calculate the last second of December 31st of the same year end_date = datetime(date_obj.year, 12, 31, 23, 59, 59) elif interval == "weekly": diff --git a/backend/pyproject.toml b/backend/pyproject.toml index df47c4e13..86f59d3c4 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "endurain" -version = "0.15.4" +version = "0.15.5" description = "Endurain API for the Endurain app" authors = ["João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com>"] readme = "README.md" diff --git a/docs/developer-guide.md b/docs/developer-guide.md index f460cc6c4..e7d25cd47 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -244,6 +244,7 @@ The table bellow details the activity types supported by Endurain. | Run | 1 | | Trail run | 2 | | Track run | 34 | +| Treadmill run | 40 | | Virtual run | 3 | | Road cycling | 4 | | Gravel cycling | 5 | diff --git a/frontend/app/package-lock.json b/frontend/app/package-lock.json index ebae8656f..b5bce3f9f 100644 --- a/frontend/app/package-lock.json +++ b/frontend/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "endurain", - "version": "0.15.4", + "version": "0.15.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "endurain", - "version": "0.15.4", + "version": "0.15.5", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.7.1", "@fortawesome/free-brands-svg-icons": "^6.7.1", diff --git a/frontend/app/package.json b/frontend/app/package.json index 21973c4da..b89a56297 100644 --- a/frontend/app/package.json +++ b/frontend/app/package.json @@ -1,6 +1,6 @@ { "name": "endurain", - "version": "0.15.4", + "version": "0.15.5", "private": true, "type": "module", "scripts": { diff --git a/frontend/app/src/components/Activities/Modals/EditActivityModalComponent.vue b/frontend/app/src/components/Activities/Modals/EditActivityModalComponent.vue index d3a84d26b..311867d97 100644 --- a/frontend/app/src/components/Activities/Modals/EditActivityModalComponent.vue +++ b/frontend/app/src/components/Activities/Modals/EditActivityModalComponent.vue @@ -81,6 +81,9 @@ + diff --git a/frontend/app/src/components/FooterComponent.vue b/frontend/app/src/components/FooterComponent.vue index 9be1f0e64..af317b2dd 100644 --- a/frontend/app/src/components/FooterComponent.vue +++ b/frontend/app/src/components/FooterComponent.vue @@ -23,7 +23,7 @@ - • v0.15.4 + • v0.15.5

t('activityItems.ebikeMountainRide'), 37: (t) => t('activityItems.iceSkate'), 38: (t) => t('activityItems.soccer'), - 39: (t) => t('activityItems.padel') + 39: (t) => t('activityItems.padel'), + 40: (t) => t('activityItems.treadmillRun') } /** @@ -282,7 +283,8 @@ export function activityTypeIsRunning(activity) { activity.activity_type === 1 || activity.activity_type === 2 || activity.activity_type === 3 || - activity.activity_type === 34 + activity.activity_type === 34 || + activity.activity_type === 40 ) } /** @@ -290,14 +292,15 @@ export function activityTypeIsRunning(activity) { * * @param {Object} activity - The activity object to check. * @param {number} activity.activity_type - The type identifier of the activity. - * @returns {boolean} Returns true if the activity is not running-related (types 1,2,3 or 34), otherwise false. + * @returns {boolean} Returns true if the activity is not running-related (types 1,2,3,34 and 40), otherwise false. */ export function activityTypeNotRunning(activity) { return ( activity.activity_type !== 1 && activity.activity_type !== 2 && activity.activity_type !== 3 && - activity.activity_type !== 34 + activity.activity_type !== 34 && + activity.activity_type !== 40 ) } @@ -710,7 +713,8 @@ export function getIcon(typeId) { 36: ['fas', 'person-biking'], 37: ['fas', 'person-skating'], 38: ['fas', 'futbol'], - 39: ['fas', 'table-tennis-paddle-ball'] + 39: ['fas', 'table-tennis-paddle-ball'], + 40: ['fas', 'person-running'], // Treadmill run icon might be better if available } return iconMap[typeId] || ['fas', 'dumbbell'] diff --git a/frontend/app/src/views/SearchView.vue b/frontend/app/src/views/SearchView.vue index 8d3bbe536..d56523282 100644 --- a/frontend/app/src/views/SearchView.vue +++ b/frontend/app/src/views/SearchView.vue @@ -258,7 +258,7 @@ const fetchGearResults = debounce(async (query) => { function updateSearchResultsBasedOnActivityType() { if (searchSelectActivityType.value === '1') { searchResults.value = searchResultsOriginal.value.filter((user) => - [1, 2, 3, 34].includes(user.activity_type) + [1, 2, 3, 34, 40].includes(user.activity_type) ) } else if (searchSelectActivityType.value === '2') { searchResults.value = searchResultsOriginal.value.filter((user) =>