New unified modal for add+edit gear + general fixes

[backend] add search gear by nickname if contains or full match logic
[frontend] add search gear by nickname if contains or full match logic
[frontend] added new unified modal for add and edit gear
[frontend] add gear now uses new unified modal
[frontend] fixed issue on adding new user if height was not inputed
This commit is contained in:
João Vitória Silva
2025-01-30 22:25:42 +00:00
parent 768af0c735
commit 64f17c61e4
17 changed files with 427 additions and 137 deletions

View File

@@ -1,6 +1,5 @@
import logging
from fastapi import HTTPException, status
from sqlalchemy import func
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from urllib.parse import unquote
@@ -14,7 +13,9 @@ import core.logger as core_logger
def get_gear_user_by_id(gear_id: int, db: Session) -> gears_schema.Gear | None:
try:
gear = db.query(gears_models.Gear).filter(gears_models.Gear.id == gear_id).first()
gear = (
db.query(gears_models.Gear).filter(gears_models.Gear.id == gear_id).first()
)
# Check if gear is None and return None if it is
if gear is None:
@@ -26,7 +27,9 @@ def get_gear_user_by_id(gear_id: int, db: Session) -> gears_schema.Gear | None:
return gear
except Exception as err:
# Log the exception
core_logger.print_to_log(f"Error in get_gear_user_by_id: {err}", "error", exc=err)
core_logger.print_to_log(
f"Error in get_gear_user_by_id: {err}", "error", exc=err
)
# Raise an HTTPException with a 500 Internal Server Error status code
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -73,7 +76,11 @@ def get_gear_users_with_pagination(
def get_gear_user(user_id: int, db: Session) -> list[gears_schema.Gear] | None:
try:
# Get the gear by user ID from the database
gears = db.query(gears_models.Gear).filter(gears_models.Gear.user_id == user_id).all()
gears = (
db.query(gears_models.Gear)
.filter(gears_models.Gear.user_id == user_id)
.all()
)
# Check if gear is None and return None if it is
if gears is None:
@@ -95,18 +102,18 @@ def get_gear_user(user_id: int, db: Session) -> list[gears_schema.Gear] | None:
) from err
def get_gear_user_by_nickname(
def get_gear_user_contains_nickname(
user_id: int, nickname: str, db: Session
) -> list[gears_schema.Gear] | None:
try:
# Unquote the nickname and change "+" to whitespace
parsed_nickname = unquote(nickname).replace("+", " ")
parsed_nickname = unquote(nickname).replace("+", " ").lower()
# Get the gear by user ID and nickname from the database
gears = (
db.query(gears_models.Gear)
.filter(
gears_models.Gear.nickname.like(f"%{parsed_nickname}%"),
func.lower(gears_models.Gear.nickname).like(f"%{parsed_nickname}%"),
gears_models.Gear.user_id == user_id,
)
.all()
@@ -124,7 +131,48 @@ def get_gear_user_by_nickname(
return gears
except Exception as err:
# Log the exception
core_logger.print_to_log(f"Error in get_gear_user_by_nickname: {err}", "error", exc=err)
core_logger.print_to_log(
f"Error in get_gear_user_contains_nickname: {err}", "error", exc=err
)
# Raise an HTTPException with a 500 Internal Server Error status code
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal Server Error",
) from err
def get_gear_user_by_nickname(
user_id: int, nickname: str, db: Session
) -> gears_schema.Gear | None:
try:
# Unquote the nickname and change "+" to whitespace
parsed_nickname = unquote(nickname).replace("+", " ").lower()
# Get the gear by user ID and nickname from the database
gear = (
db.query(gears_models.Gear)
.filter(
func.lower(gears_models.Gear.nickname) == parsed_nickname,
gears_models.Gear.user_id == user_id,
)
.first()
)
# Check if gear is None and return None if it is
if gear is None:
return None
# Serialize the gear
gear_serialized = gears_utils.serialize_gear(gear)
# return the gear
return gear_serialized
except Exception as err:
# Log the exception
core_logger.print_to_log(
f"Error in get_gear_user_by_nickname: {err}", "error", exc=err
)
# Raise an HTTPException with a 500 Internal Server Error status code
raise HTTPException(
@@ -138,7 +186,10 @@ def get_gear_by_type_and_user(gear_type: int, user_id: int, db: Session):
# Get the gear by type from the database
gear = (
db.query(gears_models.Gear)
.filter(gears_models.Gear.gear_type == gear_type, gears_models.Gear.user_id == user_id)
.filter(
gears_models.Gear.gear_type == gear_type,
gears_models.Gear.user_id == user_id,
)
.order_by(gears_models.Gear.nickname)
.all()
)
@@ -155,7 +206,9 @@ def get_gear_by_type_and_user(gear_type: int, user_id: int, db: Session):
return gear
except Exception as err:
# Log the exception
core_logger.print_to_log(f"Error in get_gear_by_type_and_user: {err}", "error", exc=err)
core_logger.print_to_log(
f"Error in get_gear_by_type_and_user: {err}", "error", exc=err
)
# Raise an HTTPException with a 500 Internal Server Error status code
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -224,7 +277,9 @@ def get_gear_by_garminconnect_id_from_user_id(
except Exception as err:
# Log the exception
core_logger.print_to_log(
f"Error in get_gear_by_garminconnect_id_from_user_id: {err}", "error", exc=err
f"Error in get_gear_by_garminconnect_id_from_user_id: {err}",
"error",
exc=err,
)
# Raise an HTTPException with a 500 Internal Server Error status code
raise HTTPException(
@@ -263,7 +318,9 @@ def create_multiple_gears(gears: list[gears_schema.Gear], user_id: int, db: Sess
db.rollback()
# Log the exception
core_logger.print_to_log(f"Error in create_multiple_gears: {err}", "error", exc=err)
core_logger.print_to_log(
f"Error in create_multiple_gears: {err}", "error", exc=err
)
# Raise an HTTPException with a 500 Internal Server Error status code
raise HTTPException(
@@ -281,8 +338,10 @@ def create_gear(gear: gears_schema.Gear, user_id: int, db: Session):
db.commit()
db.refresh(new_gear)
gear_serialized = gears_utils.serialize_gear(new_gear)
# Return the gear
return new_gear
return gear_serialized
except IntegrityError as integrity_error:
# Rollback the transaction
db.rollback()
@@ -310,7 +369,9 @@ def create_gear(gear: gears_schema.Gear, user_id: int, db: Session):
def edit_gear(gear_id: int, gear: gears_schema.Gear, db: Session):
try:
# Get the gear from the database
db_gear = db.query(gears_models.Gear).filter(gears_models.Gear.id == gear_id).first()
db_gear = (
db.query(gears_models.Gear).filter(gears_models.Gear.id == gear_id).first()
)
# Update the gear
if gear.brand is not None:
@@ -351,7 +412,9 @@ def edit_gear(gear_id: int, gear: gears_schema.Gear, db: Session):
def delete_gear(gear_id: int, db: Session):
try:
# Delete the gear
num_deleted = db.query(gears_models.Gear).filter(gears_models.Gear.id == gear_id).delete()
num_deleted = (
db.query(gears_models.Gear).filter(gears_models.Gear.id == gear_id).delete()
)
# Check if the gear was found and deleted
if num_deleted == 0:
@@ -382,7 +445,8 @@ def delete_all_strava_gear_for_user(user_id: int, db: Session):
num_deleted = (
db.query(gears_models.Gear)
.filter(
gears_models.Gear.user_id == user_id, gears_models.Gear.strava_gear_id.isnot(None)
gears_models.Gear.user_id == user_id,
gears_models.Gear.strava_gear_id.isnot(None),
)
.delete()
)

View File

@@ -85,9 +85,30 @@ async def read_gear_user_number(
@router.get(
"/nickname/{nickname}",
"/nickname/contains/{nickname}",
response_model=list[gears_schema.Gear] | None,
)
async def read_gear_user_contains_nickname(
nickname: str,
check_scopes: Annotated[
Callable, Security(session_security.check_scopes, scopes=["gears:read"])
],
token_user_id: Annotated[
int, Depends(session_security.get_user_id_from_access_token)
],
db: Annotated[
Session,
Depends(core_database.get_db),
],
):
# Return the gears
return gears_crud.get_gear_user_contains_nickname(token_user_id, nickname, db)
@router.get(
"/nickname/{nickname}",
response_model=gears_schema.Gear | None,
)
async def read_gear_user_by_nickname(
nickname: str,
check_scopes: Annotated[
@@ -129,6 +150,7 @@ async def read_gear_user_by_type(
@router.post(
"/create",
response_model=gears_schema.Gear,
status_code=201,
)
async def create_gear(
@@ -144,11 +166,8 @@ async def create_gear(
Depends(core_database.get_db),
],
):
# Create the gear
gear_created = gears_crud.create_gear(gear, token_user_id, db)
# Return the ID of the gear created
return gear_created.id
# Create the gear and return it
return gears_crud.create_gear(gear, token_user_id, db)
@router.put("/{gear_id}/edit")

View File

@@ -13,6 +13,8 @@ files:
translation: /frontend/app/src/i18n/%two_letters_code%/components/activities/modals/%original_file_name%
- source: /frontend/app/src/i18n/us/components/followers/*.json
translation: /frontend/app/src/i18n/%two_letters_code%/components/followers/%original_file_name%
- source: /frontend/app/src/i18n/us/components/gears/*.json
translation: /frontend/app/src/i18n/%two_letters_code%/components/gears/%original_file_name%
- source: /frontend/app/src/i18n/us/components/health/*.json
translation: /frontend/app/src/i18n/%two_letters_code%/components/health/%original_file_name%
- source: /frontend/app/src/i18n/us/components/health/healthWeightZone/*.json

View File

@@ -92,7 +92,7 @@ export default {
return;
}
try {
searchResults.value = await gears.getGearByNickname(query);
searchResults.value = await gears.getGearContainsNickname(query);
} catch (error) {
push.error(`${t('generalItems.errorFetchingInfo')} - ${error}`)
}

View File

@@ -0,0 +1,231 @@
<template>
<div class="modal fade" :id="action == 'add' ? 'addGearModal' : (action == 'edit' ? editGearModalId : '')" tabindex="-1" :aria-labelledby="action == 'add' ? 'addGearModal' : (action == 'edit' ? editGearModalId : '')" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="addGearModal" v-if="action == 'add'">{{ $t("gearsAddEditUserModalComponent.addEditGearModalAddTitle") }}</h1>
<h1 class="modal-title fs-5" :id='editGearModalId' v-else>{{ $t("gearsAddEditUserModalComponent.addEditGearModalEditTitle") }}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form @submit.prevent="handleSubmit">
<div class="modal-body">
<!-- brand fields -->
<label for="gearBrandAddEdit"><b>{{ $t("gearsAddEditUserModalComponent.addEditGearModalAddBrandLabel") }}</b></label>
<input class="form-control" type="text" name="gearBrandAddEdit" :placeholder='$t("gearsAddEditUserModalComponent.addEditGearModalAddBrandLabel")' v-model="newEditGearBrand" maxlength="250">
<!-- model fields -->
<label for="gearModelAddEdit"><b>{{ $t("gearsAddEditUserModalComponent.addEditGearModalAddModelLabel") }}</b></label>
<input class="form-control" type="text" name="gearModelAddEdit" :placeholder='$t("gearsAddEditUserModalComponent.addEditGearModalAddModelLabel")' v-model="newEditGearModel" maxlength="250">
<!-- nickname fields -->
<label for="gearNicknameAddEdit"><b>* {{ $t("gearsAddEditUserModalComponent.addEditGearModalAddNicknameLabel") }}</b></label>
<input class="form-control" :class="{ 'is-invalid': !isNicknameExists }" type="text" name="gearNicknameAddEdit" :placeholder='$t("gearsAddEditUserModalComponent.addEditGearModalAddNicknameLabel")' v-model="newEditGearNickname" maxlength="250" required>
<div id="validationNicknameFeedback" class="invalid-feedback" v-if="!isNicknameExists">
{{ $t("generalItems.errorNicknameAlreadyExistsFeedback") }}
</div>
<!-- gear type fields -->
<label for="gearTypeAddEdit"><b>* {{ $t("gearsAddEditUserModalComponent.addEditGearModalAddTypeLabel") }}</b></label>
<select class="form-control" name="gearTypeAddEdit" v-model="newEditGearType" required>
<option value="1">{{ $t("gearsAddEditUserModalComponent.addEditGearModalAddTypeOption1") }}</option>
<option value="2">{{ $t("gearsAddEditUserModalComponent.addEditGearModalAddTypeOption2") }}</option>
<option value="3">{{ $t("gearsAddEditUserModalComponent.addEditGearModalAddTypeOption3") }}</option>
</select>
<!-- date fields -->
<label for="gearDateAddEdit"><b>* {{ $t("gearsAddEditUserModalComponent.addEditGearModalAddDateLabel") }}</b></label>
<input class="form-control" type="date" name="gearDateAddEdit" v-model="newEditGearCreatedDate" required>
<!-- gear is_active fields -->
<label for="gearIsActiveAddEdit"><b>* {{ $t("gearsAddEditUserModalComponent.addEditGearModalAddIsActiveLabel") }}</b></label>
<select class="form-control" name="gearIsActiveAddEdit" v-model="newEditGearIsActive" required>
<option value="1">{{ $t("gearsAddEditUserModalComponent.addEditGearModalAddIsActiveOption1") }}</option>
<option value="0">{{ $t("gearsAddEditUserModalComponent.addEditGearModalAddIsActiveOption0") }}</option>
</select>
<!-- initial kilometers fields -->
<div v-if="Number(authStore?.user?.units) === 1">
<label for="gearInitialKmsAddEdit"><b>* {{ $t("gearsAddEditUserModalComponent.addEditGearModalAddIsInitialKmsLabel") }}</b></label>
<input class="form-control" type="number" step="0.1" name="gearInitialKmsAddEdit" v-model="newEditGearInitialKms" required>
</div>
<!-- initial miles fields -->
<div v-else>
<label for="gearInitialMilesAddEdit"><b>* {{ $t("gearsAddEditUserModalComponent.addEditGearModalAddIsInitialMilesLabel") }}</b></label>
<input class="form-control" type="number" step="0.1" name="gearInitialMilesAddEdit" v-model="newEditGearInitialMiles" required>
</div>
<p>* {{ $t("generalItems.requiredField") }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ $t("generalItems.buttonClose") }}</button>
<button type="submit" class="btn btn-success" name="addGear" data-bs-dismiss="modal" v-if="action == 'add'" :disabled="!isNicknameExists">{{ $t("gearsAddEditUserModalComponent.addEditGearModalAddTitle") }}</button>
<button type="submit" class="btn btn-success" name="addGear" data-bs-dismiss="modal" v-else :disabled="!isNicknameExists">{{ $t("gearsAddEditUserModalComponent.addEditGearModalEditTitle") }}</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script>
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
// Import the stores
import { useAuthStore } from "@/stores/authStore";
// import lodash
import { debounce } from 'lodash';
// Import Notivue push
import { push } from "notivue";
// Importing the services
import { gears } from '@/services/gearsService';
// Import units utils
import { kmToMiles, milesToKm } from "@/utils/unitsUtils";
export default {
props: {
action: {
type: String,
required: true,
},
gear: {
type: Object,
required: false,
},
},
emits: ["isLoadingNewGear", "createdGear", "editedGear"],
setup(props, { emit }) {
const authStore = useAuthStore();
const { t } = useI18n();
// edit gear specific variables
const editGearModalId = ref("");
const newEditGearBrand = ref('');
const newEditGearModel = ref('');
const newEditGearNickname = ref('');
const newEditGearType = ref(1);
const newEditGearCreatedDate = ref(null);
const newEditGearIsActive = ref(1);
const newEditGearInitialKms = ref(0);
const newEditGearInitialMiles = ref(0);
const isNicknameExists = ref(true);
const validateNicknameExists = debounce(async () => {
let tryValidate = false;
if (props.action === 'edit') {
if (newEditGearNickname.value !== props.gear.nickname) {
tryValidate = true;
}
} else {
if (props.action === 'add') {
if (newEditGearNickname.value !== "") {
tryValidate = true;
}
}
}
if (tryValidate) {
try {
if (await gears.getGearByNickname(newEditGearNickname.value)) {
isNicknameExists.value = false;
} else {
isNicknameExists.value = true;
}
} catch (error) {
push.error(`${t("generalItems.errorFetchingInfo")} - ${error}`);
}
} else {
isNicknameExists.value = true;
}
}, 500);
if (props.gear) {
if (props.action === 'edit') {
editGearModalId.value = `editGearModal${props.gear.id}`;
}
newEditGearBrand.value = props.gear.brand;
newEditGearModel.value = props.gear.model;
newEditGearNickname.value = props.gear.nickname;
newEditGearType.value = props.gear.type;
newEditGearCreatedDate.value = props.gear.created_date;
newEditGearIsActive.value = props.gear.is_active;
newEditGearInitialKms.value = props.gear.initial_kms;
if (props.gear.initial_kms && props.gear.initial_kms !== 0) {
newEditGearInitialMiles.value = kmToMiles(props.gear.initial_kms);
}
}
async function submitAddGearForm() {
console.log('submitAddGearForm');
// Set the loading variable to true.
emit("isLoadingNewGear", true);
try {
// Create the gear data object.
const data = {
brand: newEditGearBrand.value,
model: newEditGearModel.value,
nickname: newEditGearNickname.value,
gear_type: newEditGearType.value,
created_at: newEditGearCreatedDate.value,
is_active: newEditGearIsActive.value,
initial_kms: newEditGearInitialKms.value,
};
// Create the gear and get the created gear id.
const createdGear = await gears.createGear(data);
// Set the loading variable to false.
emit("isLoadingNewGear", false);
// Get the created gear and add it to the array.
emit("createdGear", createdGear);
// Set the success message and show the success alert.
push.success(t("gearsView.successGearAdded"));
} catch (error) {
// If there is an error, set the error message and show the error alert.
push.error(`${t("generalItems.errorFetchingInfo")} - ${error}`);
} finally {
// Set the isLoadingNewGear variable to false.
emit("isLoadingNewGear", false);
}
}
function handleSubmit() {
if (Number(authStore?.user?.units) === 1) {
if ((props.gear && newEditGearInitialKms.value !== props.gear.initial_kms) || props.action === 'add') {
newEditGearInitialMiles.value = kmToMiles(newEditGearInitialKms.initial_kms);
}
} else {
if (props.action === 'add') {
newEditGearInitialKms.value = milesToKm(newEditGearInitialMiles.value);
} else {
const miles = kmToMiles(props.gear.initial_kms);
if (miles !== newEditGearInitialMiles.value) {
newEditGearInitialKms.value = milesToKm(newEditGearInitialMiles.value);
}
}
}
if (props.action === 'add') {
submitAddGearForm();
} else {
submitEditGearForm();
}
}
// Watchers
// Watch the newEditGearNickname variable.
watch(newEditGearNickname, validateNicknameExists, { immediate: false });
return {
authStore,
t,
editGearModalId,
newEditGearBrand,
newEditGearModel,
newEditGearNickname,
newEditGearType,
newEditGearCreatedDate,
newEditGearIsActive,
newEditGearInitialKms,
newEditGearInitialMiles,
handleSubmit,
isNicknameExists,
};
},
};
</script>

View File

@@ -5,7 +5,7 @@
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="addUserModal" v-if="action == 'add'">{{ $t("settingsUsersZone.buttonAddUser") }}</h1>
<h1 class="modal-title fs-5" id="editUserModalId" v-else-if="action == 'edit'">{{ $t("usersAddEditUserModalComponent.addEditUserModalEditTitle") }}</h1>
<h1 class="modal-title fs-5" :id='editUserModalId' v-else-if="action == 'edit'">{{ $t("usersAddEditUserModalComponent.addEditUserModalEditTitle") }}</h1>
<h1 class="modal-title fs-5" id="editProfileModal" v-else>{{ $t("usersAddEditUserModalComponent.addEditUserModalEditProfileTitle") }}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
@@ -412,15 +412,19 @@ export default {
function handleSubmit() {
if (Number(authStore?.user?.units) === 1) {
if (newEditUserHeightCms.value !== props.user.height) {
if ((props.user && newEditUserHeightCms.value !== props.user.height) || props.action === 'add') {
const { feet, inches } = cmToFeetInches(newEditUserHeightCms.value);
newEditUserHeightFeet.value = feet;
newEditUserHeightInches.value = inches;
}
} else {
const { feet, inches } = cmToFeetInches(props.user.height);
if (feet !== newEditUserHeightFeet.value || inches !== newEditUserHeightInches.value) {
if (props.action === 'add') {
newEditUserHeightCms.value = feetAndInchesToCm(newEditUserHeightFeet.value, newEditUserHeightInches.value);
} else {
const { feet, inches } = cmToFeetInches(props.user.height);
if (feet !== newEditUserHeightFeet.value || inches !== newEditUserHeightInches.value) {
newEditUserHeightCms.value = feetAndInchesToCm(newEditUserHeightFeet.value, newEditUserHeightInches.value);
}
}
}

View File

@@ -11,6 +11,8 @@ import caActivitySummaryComponent from './ca/components/activities/activitySumma
import caEditActivityModalComponent from './ca/components/activities/modals/editActivityModalComponent.json';
// Followers component
import caFollowersListComponent from './ca/components/followers/followersListComponent.json';
// Gears component
import caGearsAddEditUserModalComponent from './ca/components/gears/gearsAddEditUserModalComponent.json';
// Health components
import caHealthWeightAddEditModalComponent from './ca/components/health/healthWeightZone/healthWeightAddEditModalComponent.json'
import caHealthWeightListComponent from './ca/components/health/healthWeightZone/healthWeightListComponent.json'
@@ -55,6 +57,8 @@ import deActivitySummaryComponent from './de/components/activities/activitySumma
import deEditActivityModalComponent from './de/components/activities/modals/editActivityModalComponent.json';
// Followers component
import deFollowersListComponent from './de/components/followers/followersListComponent.json';
// Gears component
import deGearsAddEditUserModalComponent from './de/components/gears/gearsAddEditUserModalComponent.json';
// Health components
import deHealthWeightAddEditModalComponent from './de/components/health/healthWeightZone/healthWeightAddEditModalComponent.json'
import deHealthWeightListComponent from './de/components/health/healthWeightZone/healthWeightListComponent.json'
@@ -100,6 +104,8 @@ import frActivitySummaryComponent from './fr/components/activities/activitySumma
import frEditActivityModalComponent from './fr/components/activities/modals/editActivityModalComponent.json';
// Followers component
import frFollowersListComponent from './fr/components/followers/followersListComponent.json';
// Gears component
import frGearsAddEditUserModalComponent from './fr/components/gears/gearsAddEditUserModalComponent.json';
// Health components
import frHealthWeightAddEditModalComponent from './fr/components/health/healthWeightZone/healthWeightAddEditModalComponent.json'
import frHealthWeightListComponent from './fr/components/health/healthWeightZone/healthWeightListComponent.json'
@@ -145,6 +151,8 @@ import ptActivitySummaryComponent from './pt/components/activities/activitySumma
import ptEditActivityModalComponent from './pt/components/activities/modals/editActivityModalComponent.json';
// Followers component
import ptFollowersListComponent from './pt/components/followers/followersListComponent.json';
// Gears component
import ptGearsAddEditUserModalComponent from './pt/components/gears/gearsAddEditUserModalComponent.json';
// Health components
import ptHealthWeightAddEditModalComponent from './pt/components/health/healthWeightZone/healthWeightAddEditModalComponent.json'
import ptHealthWeightListComponent from './pt/components/health/healthWeightZone/healthWeightListComponent.json'
@@ -189,6 +197,8 @@ import usActivitySummaryComponent from './us/components/activities/activitySumma
import usEditActivityModalComponent from './us/components/activities/modals/editActivityModalComponent.json';
// Followers component
import usFollowersListComponent from './us/components/followers/followersListComponent.json';
// Gears component
import usGearsAddEditUserModalComponent from './us/components/gears/gearsAddEditUserModalComponent.json';
// Health components
import usHealthWeightAddEditModalComponent from './us/components/health/healthWeightZone/healthWeightAddEditModalComponent.json'
import usHealthWeightListComponent from './us/components/health/healthWeightZone/healthWeightListComponent.json'
@@ -235,6 +245,8 @@ const messages = {
editActivityModalComponent: caEditActivityModalComponent,
// Followers component
followersListComponent: caFollowersListComponent,
// Gears component
gearsAddEditUserModalComponent: caGearsAddEditUserModalComponent,
// Health components
healthWeightAddEditModalComponent: caHealthWeightAddEditModalComponent,
healthSideBarComponent: caHealthSideBarComponent,
@@ -279,6 +291,8 @@ const messages = {
editActivityModalComponent: deEditActivityModalComponent,
// Followers component
followersListComponent: deFollowersListComponent,
// Gears component
gearsAddEditUserModalComponent: deGearsAddEditUserModalComponent,
// Health components
healthWeightAddEditModalComponent: deHealthWeightAddEditModalComponent,
healthSideBarComponent: deHealthSideBarComponent,
@@ -323,6 +337,8 @@ const messages = {
editActivityModalComponent: frEditActivityModalComponent,
// Followers component
followersListComponent: frFollowersListComponent,
// Gears component
gearsAddEditUserModalComponent: frGearsAddEditUserModalComponent,
// Health components
healthWeightAddEditModalComponent: frHealthWeightAddEditModalComponent,
healthSideBarComponent: frHealthSideBarComponent,
@@ -367,6 +383,8 @@ const messages = {
editActivityModalComponent: ptEditActivityModalComponent,
// Followers component
followersListComponent: ptFollowersListComponent,
// Gears component
gearsAddEditUserModalComponent: ptGearsAddEditUserModalComponent,
// Health components
healthWeightAddEditModalComponent: ptHealthWeightAddEditModalComponent,
healthSideBarComponent: ptHealthSideBarComponent,
@@ -411,6 +429,8 @@ const messages = {
editActivityModalComponent: usEditActivityModalComponent,
// Followers component
followersListComponent: usFollowersListComponent,
// Gears component
gearsAddEditUserModalComponent: usGearsAddEditUserModalComponent,
// Health components
healthWeightAddEditModalComponent: usHealthWeightAddEditModalComponent,
healthSideBarComponent: usHealthSideBarComponent,

View File

@@ -0,0 +1,17 @@
{
"addEditGearModalEditTitle": "Edit gear",
"addEditGearModalAddTitle": "Add gear",
"addEditGearModalAddBrandLabel": "Brand",
"addEditGearModalAddModelLabel": "Model",
"addEditGearModalAddNicknameLabel": "Nickname",
"addEditGearModalAddTypeLabel": "Gear type",
"addEditGearModalAddTypeOption1": "Bike",
"addEditGearModalAddTypeOption2": "Shoes",
"addEditGearModalAddTypeOption3": "Wetsuit",
"addEditGearModalAddDateLabel": "Created date",
"addEditGearModalAddIsActiveLabel": "Is active",
"addEditGearModalAddIsActiveOption1": "Active",
"addEditGearModalAddIsActiveOption0": "Inactive",
"addEditGearModalAddIsInitialKmsLabel": "Initial kms",
"addEditGearModalAddIsInitialMilesLabel": "Initial miles"
}

View File

@@ -4,15 +4,6 @@
"subTitleSearchGearByNickname": "Search gear by nickname",
"placeholderSearchGearByNickname": "Nickname",
"buttonSearchGear": "Search Gear",
"modalBrand": "Brand",
"modalModel": "Model",
"modalNickname": "Nickname",
"modalGearTypeLabel": "Gear type",
"modalGearTypeOption1Bike": "Bike",
"modalGearTypeOption2Shoes": "Shoes",
"modalGearTypeOption3Wetsuit": "Wetsuit",
"modalDateLabel": "Created date",
"modalDatePlaceholder": "Date",
"displayUserNumberOfGears1": "There is a total of ",
"displayUserNumberOfGears2": " gear(s) (",
"displayUserNumberOfGears3": " loaded):",

View File

@@ -6,6 +6,7 @@
"errorFetchingInfo": "Error fetching info",
"errorEditingInfo": "Error editing info",
"errorDeletingInfo": "Error deleting info",
"errorNicknameAlreadyExistsFeedback": "Nickname already exists",
"errorUsernameAlreadyExistsFeedback": "Username already exists",
"errorEmailNotValidFeedback": "Email not valid",
"errorEmailAlreadyExistsFeedback": "Email already exists",

View File

@@ -7,6 +7,9 @@ export const gears = {
getGearFromType(gearType) {
return fetchGetRequest(`gears/type/${gearType}`);
},
getGearContainsNickname(nickname) {
return fetchGetRequest(`gears/nickname/contains/${nickname}`);
},
getGearByNickname(nickname) {
return fetchGetRequest(`gears/nickname/${nickname}`);
},

View File

@@ -33,6 +33,10 @@ export function feetAndInchesToCm(feet, inches) {
return (totalInches * 2.54).toFixed(0);
}
export function milesToKm(miles) {
return (miles * 1.60934).toFixed(0);
}
// Metric to Metric conversions
export function metersToKm(meters) {
return (meters / 1000).toFixed(2);

View File

@@ -9,54 +9,7 @@
</a>
<!-- Add gear modal -->
<div class="modal fade" id="addGearModal" tabindex="-1" aria-labelledby="addGearModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="addGearModal">{{ $t("gearsView.buttonAddGear") }}</h1>
<!--<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>-->
</div>
<form @submit.prevent="submitAddGearForm">
<div class="modal-body">
<!-- brand fields -->
<label for="gearBrandAdd"><b>{{ $t("gearsView.modalBrand") }}</b></label>
<input class="form-control" type="text" name="gearBrandAdd" :placeholder='$t("gearsView.modalBrand")' v-model="brand" maxlength="250">
<!-- model fields -->
<label for="gearModelAdd"><b>{{ $t("gearsView.modalModel") }}</b></label>
<input class="form-control" type="text" name="gearModelAdd" :placeholder='$t("gearsView.modalModel")' v-model="model" maxlength="250">
<!-- nickname fields -->
<label for="gearNicknameAdd"><b>* {{ $t("gearsView.modalNickname") }}</b></label>
<input class="form-control" type="text" name="gearNicknameAdd" :placeholder='$t("gearsView.modalNickname")' v-model="nickname" maxlength="250" required>
<!-- gear type fields -->
<label for="gearTypeAdd"><b>* {{ $t("gearsView.modalGearTypeLabel") }}</b></label>
<select class="form-control" name="gearTypeAdd" v-model="gearType" required>
<option value="1">{{ $t("gearsView.modalGearTypeOption1Bike") }}</option>
<option value="2">{{ $t("gearsView.modalGearTypeOption2Shoes") }}</option>
<option value="3">{{ $t("gearsView.modalGearTypeOption3Wetsuit") }}</option>
</select>
<!-- date fields -->
<label for="gearDateAdd"><b>* {{ $t("gearsView.modalDateLabel") }}</b></label>
<input class="form-control" type="date" name="gearDateAdd" :placeholder='$t("gearsView.modalDatePlaceholder")' v-model="date" required>
<!-- gear is_active fields -->
<label for="gearIsActiveAdd"><b>* {{ $t("gearsView.gearIsActiveLabel") }}</b></label>
<select class="form-control" name="gearIsActiveAdd" v-model="isActive" required>
<option value="1">{{ $t("gearsView.activeState") }}</option>
<option value="0">{{ $t("gearsView.inactiveState") }}</option>
</select>
<!-- initial kilometers fields -->
<label for="gearInitialKmsAdd"><b>* {{ $t("gearsView.initialKmsLabel") }}</b></label>
<input class="form-control" type="number" step="0.2" name="gearInitialKmsAdd" v-model="initialKms" required>
<p>* {{ $t("generalItems.requiredField") }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ $t("generalItems.buttonClose") }}</button>
<button type="submit" class="btn btn-success" name="addGear" data-bs-dismiss="modal">{{ $t("gearsView.buttonAddGear") }}</button>
</div>
</form>
</div>
</div>
</div>
<GearsAddEditUserModalComponent :action="'add'" @createdGear="addGearList" @isLoadingNewGear="setIsLoadingNewGear"/>
<!-- Search gear by nickname zone -->
<br>
@@ -143,6 +96,7 @@ import NoItemsFoundComponent from '@/components/GeneralComponents/NoItemsFoundCo
import LoadingComponent from '@/components/GeneralComponents/LoadingComponent.vue';
import BackButtonComponent from '@/components/GeneralComponents/BackButtonComponent.vue';
import PaginationComponent from '@/components/GeneralComponents/PaginationComponent.vue';
import GearsAddEditUserModalComponent from '@/components/Gears/GearsAddEditUserModalComponent.vue';
// Importing the services
import { gears } from '@/services/gearsService';
@@ -154,17 +108,11 @@ export default {
LoadingComponent,
PaginationComponent,
BackButtonComponent,
GearsAddEditUserModalComponent,
},
setup() {
const { t } = useI18n();
const route = useRoute();
const brand = ref('');
const model = ref('');
const nickname = ref('');
const gearType = ref(1);
const date = ref(null);
const isActive = ref(1);
const initialKms = ref(0);
const isLoading = ref(true);
const isGearsUpdatingLoading = ref(true);
const isLoadingNewGear = ref(false)
@@ -187,46 +135,11 @@ export default {
}
try {
// Fetch the users based on the search nickname.
userGears.value = await gears.getGearByNickname(searchNickname.value);
userGears.value = await gears.getGearContainsNickname(searchNickname.value);
} catch (error) {
push.error(`${t("gearsView.errorGearNotFound")} - ${error}`);
}
}, 500);
async function submitAddGearForm() {
// Set the isLoadingNewGear variable to true.
isLoadingNewGear.value = true;
try {
// Create the gear data object.
const data = {
brand: brand.value,
model: model.value,
nickname: nickname.value,
gear_type: gearType.value,
created_at: date.value,
is_active: isActive.value,
initial_kms: initialKms.value,
};
// Create the gear and get the created gear id.
const createdGearId = await gears.createGear(data);
// Get the created gear and add it to the userGears array.
const newGear = await gears.getGearById(createdGearId);
userGears.value.unshift(newGear);
userGearsNumber.value++;
// Set the success message and show the success alert.
push.success(t("gearsView.successGearAdded"));
} catch (error) {
// If there is an error, set the error message and show the error alert.
push.error(`${t("generalItems.errorFetchingInfo")} - ${error}`);
} finally {
// Set the isLoadingNewGear variable to false.
isLoadingNewGear.value = false;
}
}
function setPageNumber(page) {
// Set the page number.
@@ -283,6 +196,20 @@ export default {
isGearsUpdatingLoading.value = false;
isLoading.value = false;
});
function addGearList(createdGear) {
userGears.value.unshift(createdGear);
userGearsNumber.value++;
}
function editUserList(editedGear) {
const index = userGears.value.findIndex((user) => user.id === editedGear.id);
userGears.value[index] = editedGear;
}
function setIsLoadingNewGear(state) {
isLoadingNewGear.value = state;
}
// Watch the search nickname variable.
watch(searchNickname, performSearch, { immediate: false });
@@ -291,25 +218,20 @@ export default {
watch(pageNumber, updateGears, { immediate: false });
return {
brand,
model,
nickname,
t,
totalPages,
pageNumber,
numRecords,
gearType,
date,
isActive,
initialKms,
isLoading,
isGearsUpdatingLoading,
isLoadingNewGear,
userGears,
userGearsNumber,
searchNickname,
t,
submitAddGearForm,
setPageNumber,
addGearList,
editUserList,
setIsLoadingNewGear,
};
},
};