Add Cardio training activity type support

Introduces 'Cardio training' as a new activity type (ID 41) across backend and frontend. Updates activity mappings, enums, goal logic, UI components, and i18n files to support the new type. Also fixes goal list initialization in SettingsUserGoals.vue.
This commit is contained in:
João Vitória Silva
2025-11-14 22:16:36 +00:00
parent 6b3cf2ce5d
commit dc3b3017d2
20 changed files with 71 additions and 15 deletions

View File

@@ -87,6 +87,7 @@ ACTIVITY_ID_TO_NAME = {
38: "Soccer",
39: "Padel",
40: "Treadmill",
41: "Cardio training",
# Add other mappings as needed based on the full list in define_activity_type comments if required
# "AlpineSki",
# "BackcountrySki",
@@ -213,6 +214,7 @@ ACTIVITY_NAME_TO_ID.update(
"padelball": 39,
"paddelball": 39,
"treadmill": 40,
"cardio_training": 41,
}
)

View File

@@ -49,6 +49,7 @@ class ActivityType(IntEnum):
SWIM (3): Swimming activities.
WALK (4): Walking or hiking activities.
STRENGTH (5): Strength or resistance training sessions.
CARDIO (6): Cardiovascular training activities.
"""
RUN = 1
@@ -56,6 +57,7 @@ class ActivityType(IntEnum):
SWIM = 3
WALK = 4
STRENGTH = 5
CARDIO = 6
class GoalType(IntEnum):

View File

@@ -85,10 +85,11 @@ def calculate_goal_progress_by_activity_type(
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],
user_goals_schema.ActivityType.CARDIO: [20, 41],
}
DEFAULT_TYPES = (19, 20)
DEFAULT_TYPES = (10, 19)
# Get activity types based on goal.activity_type, default to [10, 19, 20]
# Get activity types based on goal.activity_type, default to [10, 19]
activity_types = TYPE_MAP.get(goal.activity_type, DEFAULT_TYPES)
# Fetch all activities in a single query

20
backend/poetry.lock generated
View File

@@ -1671,6 +1671,24 @@ files = [
{file = "msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e"},
]
[[package]]
name = "mysqlclient"
version = "2.2.7"
description = "Python interface to MySQL"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "mysqlclient-2.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:2e3c11f7625029d7276ca506f8960a7fd3c5a0a0122c9e7404e6a8fe961b3d22"},
{file = "mysqlclient-2.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:a22d99d26baf4af68ebef430e3131bb5a9b722b79a9fcfac6d9bbf8a88800687"},
{file = "mysqlclient-2.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:4b4c0200890837fc64014cc938ef2273252ab544c1b12a6c1d674c23943f3f2e"},
{file = "mysqlclient-2.2.7-cp313-cp313-win_amd64.whl", hash = "sha256:201a6faa301011dd07bca6b651fe5aaa546d7c9a5426835a06c3172e1056a3c5"},
{file = "mysqlclient-2.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:199dab53a224357dd0cb4d78ca0e54018f9cee9bf9ec68d72db50e0a23569076"},
{file = "mysqlclient-2.2.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:92af368ed9c9144737af569c86d3b6c74a012a6f6b792eb868384787b52bb585"},
{file = "mysqlclient-2.2.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:977e35244fe6ef44124e9a1c2d1554728a7b76695598e4b92b37dc2130503069"},
{file = "mysqlclient-2.2.7.tar.gz", hash = "sha256:24ae22b59416d5fcce7e99c9d37548350b4565baac82f95e149cac6ce4163845"},
]
[[package]]
name = "numpy"
version = "2.3.4"
@@ -3724,4 +3742,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt
[metadata]
lock-version = "2.1"
python-versions = "^3.13"
content-hash = "e1b809d08161b2cc795d4ce36d4557d9646df6e56b12b8fddc580b8416ee1873"
content-hash = "8f8b1235645ba5165102bddff066f582eb10dff7d12e2348ac3bdffec41c1643"

View File

@@ -281,6 +281,7 @@ The table bellow details the activity types supported by Endurain.
| Stand up paddling | 32 |
| Surf | 33 |
| Soccer | 38 |
| Cardio training | 41 |
## Supported gear types

View File

@@ -195,6 +195,7 @@
activity.activity_type != 18 &&
activity.activity_type != 19 &&
activity.activity_type != 20 &&
activity.activity_type != 41 &&
activityTypeNotRacquet(activity)
"
>
@@ -237,6 +238,7 @@
activity.activity_type != 18 &&
activity.activity_type != 19 &&
activity.activity_type != 20 &&
activity.activity_type != 41 &&
activityTypeNotRacquet(activity)
"
>
@@ -265,6 +267,7 @@
activity.activity_type != 18 &&
activity.activity_type != 19 &&
activity.activity_type != 20 &&
activity.activity_type != 41 &&
activityTypeNotRacquet(activity)
"
>

View File

@@ -18,7 +18,8 @@
v-if="
(activity.activity_type === 10 ||
activity.activity_type === 19 ||
activity.activity_type === 20) &&
activity.activity_type === 20 ||
activity.activity_type === 41) &&
activityActivityWorkoutSteps &&
activityActivityWorkoutSteps.length > 0
"
@@ -29,7 +30,8 @@
v-if="
(activity.activity_type === 10 ||
activity.activity_type === 19 ||
activity.activity_type === 20) &&
activity.activity_type === 20 ||
activity.activity_type === 41) &&
activityActivityWorkoutSteps &&
activityActivityWorkoutSteps.length > 0
"
@@ -96,7 +98,8 @@
v-if="
(activity.activity_type === 10 ||
activity.activity_type === 19 ||
activity.activity_type === 20) &&
activity.activity_type === 20 ||
activity.activity_type === 41) &&
activityActivityWorkoutSteps &&
activityActivityWorkoutSteps.length > 0
"
@@ -114,7 +117,8 @@
v-if="
(activity.activity_type === 10 ||
activity.activity_type === 19 ||
activity.activity_type === 20) &&
activity.activity_type === 20 ||
activity.activity_type === 41) &&
activityActivityWorkoutSteps &&
activityActivityWorkoutSteps.length > 0
"
@@ -203,7 +207,8 @@
v-if="
(activity.activity_type === 10 ||
activity.activity_type === 19 ||
activity.activity_type === 20) &&
activity.activity_type === 20 ||
activity.activity_type === 41) &&
activityActivitySets &&
activityActivitySets.length > 0
"
@@ -240,7 +245,8 @@
v-if="
(activity.activity_type === 10 ||
activity.activity_type === 19 ||
activity.activity_type === 20) &&
activity.activity_type === 20 ||
activity.activity_type === 41) &&
activityActivitySets &&
activityActivitySets.length > 0
"

View File

@@ -136,6 +136,9 @@
<option value="20">
{{ $t('editActivityModalComponent.modalEditActivityTypeOption20') }}
</option>
<option value="41">
{{ $t('editActivityModalComponent.modalEditActivityTypeOption41') }}
</option>
<hr />
<option value="11">
{{ $t('editActivityModalComponent.modalEditActivityTypeOption11') }}

View File

@@ -79,6 +79,9 @@ function updateGoalList(goalDeletedId) {
}
function addGoalList(createdGoal) {
if (!Array.isArray(goalsArray.value)) {
goalsArray.value = []
}
goalsArray.value.unshift(createdGoal)
}

View File

@@ -76,6 +76,9 @@
<option :value="5">
{{ $t('goalsAddEditGoalModalComponent.activityTypeStrength') }}
</option>
<option :value="6">
{{ $t('goalsAddEditGoalModalComponent.activityTypeCardio') }}
</option>
</select>
<!-- goal type fields -->
<label for="goalTypeAddEdit"

View File

@@ -43,6 +43,9 @@
<span v-if="goal.activity_type == 5">{{
$t('goalsAddEditGoalModalComponent.activityTypeStrength')
}}</span>
<span v-if="goal.activity_type == 6">{{
$t('goalsAddEditGoalModalComponent.activityTypeCardio')
}}</span>
</div>
<span v-if="goal.interval == 'daily'">{{
$t('goalsAddEditGoalModalComponent.intervalOption1')

View File

@@ -19,6 +19,9 @@
<span v-if="goal.activity_type == 5">{{
$t('userGoalsStatsComponent.activityTypeStrength')
}}</span>
<span v-if="goal.activity_type == 6">{{
$t('userGoalsStatsComponent.activityTypeCardio')
}}</span>
<span> | </span>
<span v-if="goal.interval == 'daily'">{{
$t('userGoalsStatsComponent.intervalOption1')

View File

@@ -47,6 +47,7 @@
"modalEditActivityTypeOption38": "Soccer",
"modalEditActivityTypeOption39": "Padel",
"modalEditActivityTypeOption40": "Treadmill run",
"modalEditActivityTypeOption41": "Cardio training",
"modalEditActivityVisibilityLabel": "Visibility",
"modalEditActivityVisibilityOption0": "Public",
"modalEditActivityVisibilityOption1": "Followers",

View File

@@ -39,5 +39,6 @@
"soccer": "Soccer",
"padel": "Padel",
"treadmillRun": "Treadmill run",
"cardioTraining": "Cardio training",
"labelWorkout": " workout"
}

View File

@@ -47,6 +47,7 @@
"modalEditActivityTypeOption38": "Soccer",
"modalEditActivityTypeOption39": "Padel",
"modalEditActivityTypeOption40": "Treadmill run",
"modalEditActivityTypeOption41": "Cardio training",
"modalEditActivityVisibilityLabel": "Visibility",
"modalEditActivityVisibilityOption0": "Public",
"modalEditActivityVisibilityOption1": "Followers",

View File

@@ -12,6 +12,7 @@
"activityTypeSwim": "Swim",
"activityTypeWalk": "Walk",
"activityTypeStrength": "Strength",
"activityTypeCardio": "Cardio",
"addEditGoalModalGoalTypeLabel": "Type",
"addEditGoalModalCaloriesLabel": "Calories",
"addEditGoalModalCaloriesPlaceholder": "Target calories",
@@ -27,4 +28,4 @@
"addEditGoalModalErrorAddGoal": "Error adding goal",
"addEditGoalModalSuccessEditGoal": "Goal edited successfully",
"addEditGoalModalErrorEditGoal": "Error editing goal"
}
}

View File

@@ -5,9 +5,10 @@
"activityTypeSwim": "Swim",
"activityTypeWalk": "Walk",
"activityTypeStrength": "Strength",
"activityTypeCardio": "Cardio",
"intervalOption1": "Daily",
"intervalOption2": "Weekly",
"intervalOption3": "Monthly",
"intervalOption4": "Yearly",
"activities": "activities"
}
}

View File

@@ -20,6 +20,7 @@
"searchSelectActivityType14": "Surf",
"searchSelectActivityType15": "Ice skate",
"searchSelectActivityType16": "Soccer",
"searchSelectActivityType17": "Cardio",
"searchSelectGearType0": "All",
"searchSelectGearType1": "Bike",
"searchSelectGearType2": "Shoes",
@@ -34,4 +35,4 @@
"errorFetchingUserWithUsernameContains": "Error fetching user with username contains logic",
"errorFetchingActivityWithNameContains": "Error fetching activity with name contains logic",
"errorFetchingGearWithNicknameContains": "Error fetching gear with nickname contains logic"
}
}

View File

@@ -8,7 +8,7 @@ import { formatDateMed, formatTime, formatSecondsToMinutes } from '@/utils/dateT
*/
const ACTIVITY_TYPES = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40
28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41
]
/**
@@ -61,7 +61,8 @@ const activityLabelMap = {
37: (t) => t('activityItems.iceSkate'),
38: (t) => t('activityItems.soccer'),
39: (t) => t('activityItems.padel'),
40: (t) => t('activityItems.treadmillRun')
40: (t) => t('activityItems.treadmillRun'),
41: (t) => t('activityItems.cardioTraining')
}
/**
@@ -715,6 +716,7 @@ export function getIcon(typeId) {
38: ['fas', 'futbol'],
39: ['fas', 'table-tennis-paddle-ball'],
40: ['fas', 'person-running'], // Treadmill run icon might be better if available
41: ['fas', 'heart-pulse'], // Cardio training icon might be better if available
}
return iconMap[typeId] || ['fas', 'dumbbell']

View File

@@ -270,7 +270,7 @@ function updateSearchResultsBasedOnActivityType() {
)
} else if (searchSelectActivityType.value === '4') {
searchResults.value = searchResultsOriginal.value.filter((user) =>
[10, 19, 20].includes(user.activity_type)
[10, 19, 20, 41].includes(user.activity_type)
)
} else if (searchSelectActivityType.value === '5') {
searchResults.value = searchResultsOriginal.value.filter((user) =>