Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions src/components/Exercises/Detail/Head/ExerciseDeleteDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import React from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { deleteExercise, deleteExerciseTranslation, getExercise } from "services";
import { ExerciseSearchResponse } from "services/responseType";

export function ExerciseDeleteDialog(props: {
onClose: () => void,
Expand Down Expand Up @@ -91,10 +90,10 @@ export function ExerciseDeleteDialog(props: {
<p>{t('exercises.replacementsSearch')}</p>

<NameAutocompleter
callback={(exercise: ExerciseSearchResponse | null) => {
callback={(exercise: Exercise | null) => {
if (exercise !== null) {
setReplacementId(exercise.data.base_id);
loadCurrentReplacement(exercise.data.base_id);
setReplacementId(exercise.id!);
loadCurrentReplacement(exercise.id!);
}
}}
/>
Expand Down
8 changes: 4 additions & 4 deletions src/components/Exercises/ExerciseOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useExercisesQuery } from "components/Exercises/queries";
import React, { useContext, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import { ExerciseSearchResponse } from "services/responseType";
import { Exercise } from "components/Exercises/models/exercise";
import { makeLink, WgerLink } from "utils/url";
import { ExerciseFiltersContext } from './Filter/ExerciseFiltersContext';
import { FilterDrawer } from './Filter/FilterDrawer';
Expand Down Expand Up @@ -135,12 +135,12 @@ export const ExerciseOverviewList = () => {
page * ITEMS_PER_PAGE
);

const exerciseAdded = (exerciseResponse: ExerciseSearchResponse | null) => {
if (!exerciseResponse) {
const exerciseAdded = (exercise: Exercise | null) => {
if (!exercise) {
return;
}

navigate(makeLink(WgerLink.EXERCISE_DETAIL, i18n.language, { id: exerciseResponse.data.base_id }));
navigate(makeLink(WgerLink.EXERCISE_DETAIL, i18n.language, { id: exercise.id }));
};

return (
Expand Down
80 changes: 41 additions & 39 deletions src/components/Exercises/Filter/NameAutcompleter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,25 @@
Switch,
TextField,
} from "@mui/material";
import { Exercise } from "components/Exercises/models/exercise";
import { SERVER_URL } from "config";
import debounce from "lodash/debounce";
import * as React from "react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { getExercise, searchExerciseTranslations } from "services";
import { ExerciseSearchResponse } from "services/responseType";
import { searchExerciseTranslations } from "services";
import { LANGUAGE_SHORT_ENGLISH } from "utils/consts";

type NameAutocompleterProps = {
callback: (exerciseResponse: ExerciseSearchResponse | null) => void;
callback: (exercise: Exercise | null) => void;
loadExercise?: boolean;
};

export function NameAutocompleter({ callback, loadExercise }: NameAutocompleterProps) {
const [value, setValue] = React.useState<ExerciseSearchResponse | null>(null);
const [value, setValue] = React.useState<Exercise | null>(null);
const [inputValue, setInputValue] = React.useState("");
const [searchEnglish, setSearchEnglish] = useState<boolean>(true);
const [options, setOptions] = React.useState<readonly ExerciseSearchResponse[]>([]);
const [options, setOptions] = React.useState<readonly Exercise[]>([]);
const [t, i18n] = useTranslation();

loadExercise = loadExercise === undefined ? false : loadExercise;
Expand Down Expand Up @@ -61,7 +61,7 @@
<>
<Autocomplete
id="exercise-name-autocomplete"
getOptionLabel={(option) => option.value}
getOptionLabel={(option) => option.getTranslation().name}
data-testid="autocomplete"
filterOptions={(x) => x}
options={options}
Expand All @@ -70,13 +70,10 @@
filterSelectedOptions
value={value}
noOptionsText={t("noResults")}
isOptionEqualToValue={(option, value) => option.value === value.value}
onChange={async (event: React.SyntheticEvent, newValue: ExerciseSearchResponse | null) => {
isOptionEqualToValue={(option, value) => option.id === value.id}
onChange={async (event: any, newValue: Exercise | null) => {

Check failure on line 74 in src/components/Exercises/Filter/NameAutcompleter.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
setOptions(newValue ? [newValue, ...options] : options);
setValue(newValue);
if (loadExercise && newValue !== null) {
newValue.exercise = await getExercise(newValue.data.base_id);
}
callback(newValue);
}}
onInputChange={(event, newInputValue) => {
Expand All @@ -102,34 +99,39 @@
}}
/>
)}
renderOption={(props, option, state) => (
<li
{...props}
key={`exercise-${state.index}-${option.data.id}`}
data-testid={`autocompleter-result-${option.data.base_id}`}
>
<ListItem disablePadding component="div">
<ListItemIcon>
{option.data.image ? (
<Avatar alt="" src={`${SERVER_URL}${option.data.image}`} variant="rounded" />
) : (
<PhotoIcon fontSize="large" />
)}
</ListItemIcon>
<ListItemText
primary={option.value}
slotProps={{
primary: {
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
},
}}
secondary={option.data.category}
/>
</ListItem>
</li>
)}
renderOption={(props, option, state) => {
const translation = option.getTranslation();
const mainImage = option.mainImage;

return (
<li
{...props}
key={`exercise-${state.index}-${option.id}`}
data-testid={`autocompleter-result-${option.id}`}
>
<ListItem disablePadding component="div">
<ListItemIcon>
{mainImage ? (
<Avatar alt="" src={`${SERVER_URL}${mainImage.url}`} variant="rounded" />
) : (
<PhotoIcon fontSize="large" />
)}
</ListItemIcon>
<ListItemText
primary={translation.name}
slotProps={{
primary: {
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
},
}}
secondary={option.category.name}
/>
</ListItem>
</li>
);
}}
/>
{i18n.language !== LANGUAGE_SHORT_ENGLISH && (
<FormGroup>
Expand Down
3 changes: 2 additions & 1 deletion src/components/Exercises/Filter/NameAutocompleter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NameAutocompleter } from "components/Exercises/Filter/NameAutcompleter"
import React from 'react';
import { searchExerciseTranslations } from "services";
import { searchResponse } from "tests/exercises/searchResponse";
import { Exercise } from "components/Exercises/models/exercise";

jest.mock("services");
const mockCallback = jest.fn();
Expand Down Expand Up @@ -71,6 +72,6 @@ describe("Test the NameAutocompleter component", () => {
});

// Assert
expect(mockCallback).toHaveBeenLastCalledWith(searchResponse[0]);
expect(mockCallback).toHaveBeenCalledWith(expect.any(Exercise));
});
});
31 changes: 23 additions & 8 deletions src/services/exerciseTranslation.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import axios from 'axios';
import { Exercise, ExerciseAdapter } from "components/Exercises/models/exercise";
import { Translation, TranslationAdapter } from "components/Exercises/models/translation";
import { ENGLISH_LANGUAGE_CODE, LANGUAGE_SHORT_ENGLISH } from "utils/consts";
import { makeHeader, makeUrl } from "utils/url";
import { ExerciseSearchResponse, ExerciseSearchType, ResponseType } from "./responseType";
import { ResponseType } from "./responseType";

export const EXERCISE_PATH = 'exercise';
export const EXERCISE_TRANSLATION_PATH = 'exercise-translation';
export const EXERCISE_SEARCH_PATH = 'exercise/search';


/*
Expand All @@ -24,19 +24,34 @@


/*
* Fetch all exercise translations for a given exercise base
* Search for exercises by name using the exerciseinfo endpoint
*/
export const searchExerciseTranslations = async (name: string, languageCode: string = ENGLISH_LANGUAGE_CODE, searchEnglish: boolean = true,): Promise<ExerciseSearchResponse[]> => {
export const searchExerciseTranslations = async (name: string,languageCode: string = ENGLISH_LANGUAGE_CODE,searchEnglish: boolean = true): Promise<Exercise[]> => {
const languages = [languageCode];
if (languageCode !== LANGUAGE_SHORT_ENGLISH && searchEnglish) {
languages.push(LANGUAGE_SHORT_ENGLISH);
}

const url = makeUrl('exerciseinfo', {
query: {
name__search: name,
language__code: languages.join(','),
limit: 50,
}
});

const url = makeUrl(EXERCISE_SEARCH_PATH, { query: { term: name, language: languages.join(',') } });

const { data } = await axios.get<ExerciseSearchType>(url);
return data.suggestions;
try {
const { data } = await axios.get<ResponseType<any>>(url);

Check failure on line 44 in src/services/exerciseTranslation.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type

if (!data || !data.results || !Array.isArray(data.results)) {
return [];
}

const adapter = new ExerciseAdapter();
return data.results.map((item: any) => adapter.fromJson(item));

Check failure on line 51 in src/services/exerciseTranslation.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
} catch (error) {
return [];
}
};


Expand Down
69 changes: 47 additions & 22 deletions src/tests/exercises/searchResponse.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,48 @@
export const searchResponse = [
{
"value": "Crunches an Negativbank",
"data": {
"id": 1149,
"base_id": 998,
"name": "Crunches an Negativbank",
"category": "Bauch",
"image": null,
"image_thumbnail": null
}
}, {
"value": "Crunches am Seil",
"data": {
"id": 1213,
"base_id": 979,
"name": "Crunches am Seil",
"category": "Brust",
"image": null,
"image_thumbnail": null
}
}
import { Exercise } from "components/Exercises/models/exercise";
import { Category } from "components/Exercises/models/category";
import { Translation } from "components/Exercises/models/translation";

export const searchResponse: Exercise[] = [
new Exercise(
998, // id
"uuid-998", // uuid
new Category(8, "Bauch"), // category
[], // equipment
[], // muscles
[], // musclesSecondary
[], // images
null, // variationId
[
new Translation(
1149, // id
"uuid-1149", // uuid
"Crunches an Negativbank", // name
"", // description
1 // language (German)
)
], // translations
[], // videos
[] // authors
),
new Exercise(
979, // id
"uuid-979", // uuid
new Category(11, "Brust"), // category
[], // equipment
[], // muscles
[], // musclesSecondary
[], // images
null, // variationId
[
new Translation(
1213, // id
"uuid-1213", // uuid
"Crunches am Seil", // name
"", // description
1 // language (German)
)
], // translations
[], // videos
[] // authors
)
];
Loading