@wger-project/react-components 25.10.16 → 25.11.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +1 -1
  2. package/build/chunks/{browser-ponyfill-DL_vVusK.js → browser-ponyfill-CyEkMYuR.js} +3 -3
  3. package/build/chunks/{browser-ponyfill-DL_vVusK.js.map → browser-ponyfill-CyEkMYuR.js.map} +1 -1
  4. package/build/locales/de/translation.json +334 -334
  5. package/build/locales/en/translation.json +12 -2
  6. package/build/locales/fil/translation.json +3 -0
  7. package/build/locales/fr/translation.json +347 -337
  8. package/build/locales/hr/translation.json +344 -244
  9. package/build/locales/it/translation.json +333 -333
  10. package/build/locales/ko/translation.json +327 -327
  11. package/build/locales/pt_PT/translation.json +330 -330
  12. package/build/locales/sk/translation.json +26 -1
  13. package/build/locales/sl/translation.json +321 -321
  14. package/build/locales/ta/translation.json +325 -325
  15. package/build/locales/uk/translation.json +344 -334
  16. package/build/locales/zh_Hans/translation.json +332 -332
  17. package/build/main.js +137 -236
  18. package/build/main.js.map +1 -1
  19. package/package.json +13 -13
  20. package/src/components/BodyWeight/Form/WeightForm.tsx +3 -4
  21. package/src/components/BodyWeight/WeightChart/index.tsx +26 -29
  22. package/src/components/Exercises/Detail/Head/ExerciseDeleteDialog.tsx +3 -4
  23. package/src/components/Exercises/ExerciseOverview.tsx +4 -4
  24. package/src/components/Exercises/Filter/NameAutcompleter.tsx +41 -39
  25. package/src/components/Exercises/Filter/NameAutocompleter.test.tsx +2 -1
  26. package/src/components/Measurements/Screens/MeasurementCategoryDetail.test.tsx +4 -2
  27. package/src/components/Measurements/widgets/MeasurementChart.tsx +27 -30
  28. package/src/components/Nutrition/components/BmiCalculator.tsx +40 -42
  29. package/src/components/Nutrition/widgets/IngredientAutcompleter.tsx +1 -1
  30. package/src/components/Nutrition/widgets/charts/MacrosPieChart.tsx +17 -19
  31. package/src/components/Nutrition/widgets/charts/NutritionDiaryChart.tsx +39 -38
  32. package/src/components/Nutrition/widgets/charts/NutritionalValuesDashboardChart.tsx +27 -29
  33. package/src/components/Nutrition/widgets/charts/NutritionalValuesPlannedLoggedChart.tsx +20 -19
  34. package/src/components/Nutrition/widgets/forms/NutritionDiaryEntryForm.tsx +15 -4
  35. package/src/components/WorkoutRoutines/Detail/RoutineDetailsTable.tsx +3 -3
  36. package/src/components/WorkoutRoutines/Detail/WorkoutStats.tsx +17 -19
  37. package/src/components/WorkoutRoutines/models/Day.ts +16 -6
  38. package/src/components/WorkoutRoutines/models/SlotEntry.ts +7 -2
  39. package/src/components/WorkoutRoutines/widgets/DayDetails.tsx +4 -4
  40. package/src/components/WorkoutRoutines/widgets/LogWidgets.tsx +35 -38
  41. package/src/components/WorkoutRoutines/widgets/RoutineDetailsCard.tsx +8 -11
  42. package/src/components/WorkoutRoutines/widgets/SlotDetails.tsx +4 -4
  43. package/src/components/WorkoutRoutines/widgets/forms/DayForm.tsx +21 -9
  44. package/src/components/WorkoutRoutines/widgets/forms/DayTypeSelect.tsx +66 -0
  45. package/src/components/WorkoutRoutines/widgets/forms/SessionForm.test.tsx +0 -2
  46. package/src/components/WorkoutRoutines/widgets/forms/SessionLogsForm.tsx +4 -4
  47. package/src/components/WorkoutRoutines/widgets/forms/SlotEntryForm.test.tsx +12 -10
  48. package/src/components/WorkoutRoutines/widgets/forms/SlotEntryForm.tsx +2 -1
  49. package/src/components/WorkoutRoutines/widgets/forms/SlotForm.tsx +1 -1
  50. package/src/services/exerciseTranslation.ts +24 -9
  51. package/src/services/responseType.ts +3 -29
  52. package/src/tests/exercises/searchResponse.ts +47 -22
@@ -15,8 +15,9 @@ import LoadingButton from "@mui/material/Button";
15
15
  import Grid from '@mui/material/Grid';
16
16
  import { WgerTextField } from "components/Common/forms/WgerTextField";
17
17
  import { DeleteConfirmationModal } from "components/Core/Modals/DeleteConfirmationModal";
18
- import { Day } from "components/WorkoutRoutines/models/Day";
18
+ import { Day, DayType } from "components/WorkoutRoutines/models/Day";
19
19
  import { useDeleteDayQuery, useEditDayQuery } from "components/WorkoutRoutines/queries";
20
+ import { DayTypeSelect } from "components/WorkoutRoutines/widgets/forms/DayTypeSelect";
20
21
  import { DefaultRoundingMenu } from "components/WorkoutRoutines/widgets/forms/RoutineForm";
21
22
  import { Form, Formik } from "formik";
22
23
  import React, { useState } from "react";
@@ -74,21 +75,25 @@ export const DayForm = (props: {
74
75
  description: Yup.string()
75
76
  .max(descriptionMaxLength, t('forms.maxLength', { chars: descriptionMaxLength })),
76
77
  isRest: Yup.boolean(),
77
- needsLogsToAdvance: Yup.boolean()
78
+ needsLogsToAdvance: Yup.boolean(),
79
+ type: Yup.string(),
78
80
  });
79
81
 
80
82
  const handleSubmit = (values: Partial<{
81
83
  name: string,
82
84
  description: string,
83
85
  isRest: boolean,
84
- needsLogsToAdvance: boolean
85
- }>) => editDayQuery.mutate(Day.clone(
86
+ needsLogsToAdvance: boolean,
87
+ type: string
88
+ }>) =>
89
+ editDayQuery.mutate(Day.clone(
86
90
  props.day,
87
91
  {
88
92
  ...(values.name !== undefined && { name: values.name }),
89
93
  ...(values.description !== undefined && { description: values.description }),
90
94
  ...({ isRest: values.isRest }),
91
95
  ...(values.needsLogsToAdvance !== undefined && { needLogsToAdvance: values.needsLogsToAdvance }),
96
+ ...(values.type !== undefined && { type: values.type as DayType }),
92
97
  })
93
98
  );
94
99
 
@@ -98,7 +103,8 @@ export const DayForm = (props: {
98
103
  name: props.day.name,
99
104
  description: props.day.description,
100
105
  isRest: props.day.isRest,
101
- needsLogsToAdvance: props.day.needLogsToAdvance
106
+ needsLogsToAdvance: props.day.needLogsToAdvance,
107
+ type: props.day.type,
102
108
  }}
103
109
  validationSchema={validationSchema}
104
110
  onSubmit={(values, { setSubmitting }) => {
@@ -110,19 +116,25 @@ export const DayForm = (props: {
110
116
  {(formik) => (
111
117
  <Form>
112
118
  <Grid container spacing={2}>
113
- <Grid size={{ xs: 12, sm: 6 }}>
119
+ <Grid size={{ xs: 12, sm: 6, md: 4 }}>
114
120
  <WgerTextField
115
121
  fieldName="name"
116
122
  title="Name"
117
123
  fieldProps={{ disabled: isRestDay }}
118
124
  />
119
125
  </Grid>
120
- <Grid size={{ xs: 6, sm: 2 }}>
126
+ <Grid size={{ xs: 12, sm: 6, md: 3 }}>
127
+ <DayTypeSelect
128
+ fieldName="type"
129
+ title="Type"
130
+ />
131
+ </Grid>
132
+ <Grid size={{ xs: 12, sm: 6, md: 2 }}>
121
133
  <FormControlLabel
122
134
  control={<Switch checked={isRestDay} onChange={handleRestDayChange} />}
123
135
  label={t('routines.restDay')} />
124
136
  </Grid>
125
- <Grid size={{ xs: 6, sm: 4 }}>
137
+ <Grid size={{ xs: 12, sm: 4, md: 3 }}>
126
138
  <FormControlLabel
127
139
  disabled={isRestDay}
128
140
  control={<Switch
@@ -188,7 +200,7 @@ export const DayForm = (props: {
188
200
  </Dialog>
189
201
 
190
202
  <DeleteConfirmationModal
191
- title={t('deleteConfirmation', { name: props.day.getDisplayName() })}
203
+ title={t('deleteConfirmation', { name: props.day.displayName })}
192
204
  message={t('routines.deleteDayConfirmation')}
193
205
  isOpen={openDeleteDialog}
194
206
  closeFn={handleCancelDeleteDay}
@@ -0,0 +1,66 @@
1
+ import MenuItem from "@mui/material/MenuItem";
2
+ import TextField from "@mui/material/TextField";
3
+ import { useField } from "formik";
4
+ import { useTranslation } from "react-i18next";
5
+
6
+ interface DayTypeSelectProps {
7
+ fieldName: string,
8
+ title: string,
9
+ }
10
+
11
+ export const DayTypeSelect = (props: DayTypeSelectProps) => {
12
+ const { t } = useTranslation();
13
+ const [field, meta] = useField(props.fieldName);
14
+
15
+ const options = [
16
+ {
17
+ value: 'custom',
18
+ label: t('routines.day.custom'),
19
+ },
20
+ {
21
+ value: 'enom',
22
+ label: t('routines.day.enom'),
23
+ },
24
+ {
25
+ value: 'amrap',
26
+ label: t('routines.day.amrap'),
27
+ },
28
+ {
29
+ value: 'hiit',
30
+ label: t('routines.day.hiit'),
31
+ },
32
+ {
33
+ value: 'tabata',
34
+ label: t('routines.day.tabata'),
35
+ },
36
+ {
37
+ value: 'edt',
38
+ label: t('routines.day.edt'),
39
+ },
40
+ {
41
+ value: 'rft',
42
+ label: t('routines.day.rft'),
43
+ },
44
+ {
45
+ value: 'afap',
46
+ label: t('routines.day.afap'),
47
+ }
48
+ ] as const;
49
+
50
+
51
+ return <>
52
+ <TextField
53
+ fullWidth
54
+ select
55
+ label={t('routines.set.type')}
56
+ variant="standard"
57
+ {...field}
58
+ >
59
+ {options!.map((option) => (
60
+ <MenuItem key={option.value} value={option.value}>
61
+ {option.value.toUpperCase()} - <small>{option.label}</small>
62
+ </MenuItem>
63
+ ))}
64
+ </TextField>
65
+ </>;
66
+ };
@@ -120,8 +120,6 @@ describe('SessionForm', () => {
120
120
  </BrowserRouter>
121
121
  );
122
122
 
123
- screen.logTestingPlaygroundURL();
124
-
125
123
  // Assert
126
124
  await waitFor(() => {
127
125
  // screen.logTestingPlaygroundURL();
@@ -6,6 +6,7 @@ import Grid from '@mui/material/Grid';
6
6
  import { WgerTextField } from "components/Common/forms/WgerTextField";
7
7
  import { LoadingPlaceholder } from "components/Core/LoadingWidget/LoadingWidget";
8
8
  import { NameAutocompleter } from "components/Exercises/Filter/NameAutcompleter";
9
+ import { Exercise } from "components/Exercises/models/exercise";
9
10
  import { useLanguageQuery } from "components/Exercises/queries";
10
11
  import { RIR_VALUES_SELECT } from "components/WorkoutRoutines/models/BaseConfig";
11
12
  import { LogEntryForm } from "components/WorkoutRoutines/models/WorkoutLog";
@@ -15,7 +16,6 @@ import { DateTime } from "luxon";
15
16
  import React, { useState } from 'react';
16
17
  import { useTranslation } from "react-i18next";
17
18
  import { getLanguageByShortName } from "services";
18
- import { ExerciseSearchResponse } from "services/responseType";
19
19
  import { REP_UNIT_REPETITIONS, SNACKBAR_AUTO_HIDE_DURATION } from "utils/consts";
20
20
  import * as yup from "yup";
21
21
 
@@ -96,11 +96,11 @@ export const SessionLogsForm = ({ dayId, routineId, selectedDate }: SessionLogsF
96
96
  setSnackbarOpen(true);
97
97
  };
98
98
 
99
- const handleCallback = async (exerciseResponse: ExerciseSearchResponse | null, formik: FormikProps<{
99
+ const handleCallback = async (exercise: Exercise | null, formik: FormikProps<{
100
100
  logs: LogEntryForm[]
101
101
  }>) => {
102
102
 
103
- if (exerciseResponse === null) {
103
+ if (exercise === null) {
104
104
  return;
105
105
  }
106
106
 
@@ -115,7 +115,7 @@ export const SessionLogsForm = ({ dayId, routineId, selectedDate }: SessionLogsF
115
115
  repetitionsTarget: '',
116
116
  rir: '',
117
117
  rirTarget: '',
118
- exercise: exerciseResponse.exercise!,
118
+ exercise: exercise,
119
119
  };
120
120
  }
121
121
  return log;
@@ -40,16 +40,18 @@ describe('SlotEntryTypeField', () => {
40
40
  const dropdown = screen.getByRole('combobox', { name: 'routines.set.type' });
41
41
  await user.click(dropdown);
42
42
 
43
- expect(screen.queryAllByText('routines.set.normalSet')).toHaveLength(2); // One in the options menu, one in the selected value
44
- expect(screen.getByText('routines.set.dropSet')).toBeInTheDocument();
45
- expect(screen.getByText('routines.set.myo')).toBeInTheDocument();
46
- expect(screen.getByText('routines.set.partial')).toBeInTheDocument();
47
- expect(screen.getByText('routines.set.forced')).toBeInTheDocument();
48
- expect(screen.getByText('routines.set.tut')).toBeInTheDocument();
49
- expect(screen.getByText('routines.set.iso')).toBeInTheDocument();
50
- expect(screen.getByText('routines.set.jump')).toBeInTheDocument();
51
-
52
- const myoOption = screen.getByRole('option', { name: 'routines.set.myo' });
43
+ // One in the options menu, one in the selected value
44
+ expect(screen.queryAllByText(/routines\.set\.normalSet/)).toHaveLength(2);
45
+ expect(screen.getByText(/routines\.set\.dropSet/)).toBeInTheDocument();
46
+ expect(screen.getByText(/routines\.set\.myo/)).toBeInTheDocument();
47
+ expect(screen.getByText(/routines\.set\.partial/)).toBeInTheDocument();
48
+ expect(screen.getByText(/routines\.set\.forced/)).toBeInTheDocument();
49
+ expect(screen.getByText(/routines\.set\.tut/)).toBeInTheDocument();
50
+ expect(screen.getByText(/routines\.set\.iso/)).toBeInTheDocument();
51
+ expect(screen.getByText(/routines\.set\.jump/)).toBeInTheDocument();
52
+
53
+
54
+ const myoOption = screen.getByRole('option', { name: /routines\.set\.myo/ });
53
55
  await user.click(myoOption);
54
56
  expect(mockEditSlotEntry).toHaveBeenCalledWith(SlotEntry.clone(testDayLegs.slots[0].entries[0], { type: 'myo' }));
55
57
  });
@@ -55,6 +55,7 @@ export const SlotEntryTypeField = (props: { slotEntry: SlotEntry, routineId: num
55
55
  editQuery.mutate(SlotEntry.clone(props.slotEntry, { type: newValue as SlotEntryType }));
56
56
  };
57
57
 
58
+
58
59
  return <>
59
60
  <TextField
60
61
  fullWidth
@@ -67,7 +68,7 @@ export const SlotEntryTypeField = (props: { slotEntry: SlotEntry, routineId: num
67
68
  >
68
69
  {options!.map((option) => (
69
70
  <MenuItem key={option.value} value={option.value}>
70
- {option.label}
71
+ {option.value.toUpperCase()} - {option.label}
71
72
  </MenuItem>
72
73
  ))}
73
74
  </TextField>
@@ -26,7 +26,7 @@ export const SlotForm = (props: { slot: Slot, routineId: number }) => {
26
26
  size={"small"}
27
27
  value={slotComment}
28
28
  disabled={editSlotQuery.isPending}
29
- onChange={(e) => handleChange(e.target.value)}
29
+ onChange={(e) => setSlotComment(e.target.value)}
30
30
  onBlur={handleBlur}
31
31
  slotProps={{
32
32
  input: { endAdornment: editSlotQuery.isPending && <LoadingProgressIcon /> }
@@ -1,12 +1,12 @@
1
1
  import axios from 'axios';
2
+ import { Exercise, ExerciseAdapter } from "components/Exercises/models/exercise";
2
3
  import { Translation, TranslationAdapter } from "components/Exercises/models/translation";
3
4
  import { ENGLISH_LANGUAGE_CODE, LANGUAGE_SHORT_ENGLISH } from "utils/consts";
4
5
  import { makeHeader, makeUrl } from "utils/url";
5
- import { ExerciseSearchResponse, ExerciseSearchType, ResponseType } from "./responseType";
6
+ import { ResponseType } from "./responseType";
6
7
 
7
8
  export const EXERCISE_PATH = 'exercise';
8
9
  export const EXERCISE_TRANSLATION_PATH = 'exercise-translation';
9
- export const EXERCISE_SEARCH_PATH = 'exercise/search';
10
10
 
11
11
 
12
12
  /*
@@ -15,7 +15,7 @@ export const EXERCISE_SEARCH_PATH = 'exercise/search';
15
15
  export const getExerciseTranslations = async (id: number): Promise<Translation[]> => {
16
16
  const url = makeUrl(EXERCISE_PATH, { query: { exercise: id } });
17
17
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
- const { data } = await axios.get<ResponseType<any>>(url, {
18
+ const { data } = await axios.get<ResponseType<Translation>>(url, {
19
19
  headers: makeHeader(),
20
20
  });
21
21
  const adapter = new TranslationAdapter();
@@ -24,19 +24,34 @@ export const getExerciseTranslations = async (id: number): Promise<Translation[]
24
24
 
25
25
 
26
26
  /*
27
- * Fetch all exercise translations for a given exercise base
27
+ * Search for exercises by name using the exerciseinfo endpoint
28
28
  */
29
- export const searchExerciseTranslations = async (name: string, languageCode: string = ENGLISH_LANGUAGE_CODE, searchEnglish: boolean = true,): Promise<ExerciseSearchResponse[]> => {
29
+ export const searchExerciseTranslations = async (name: string,languageCode: string = ENGLISH_LANGUAGE_CODE,searchEnglish: boolean = true): Promise<Exercise[]> => {
30
30
  const languages = [languageCode];
31
31
  if (languageCode !== LANGUAGE_SHORT_ENGLISH && searchEnglish) {
32
32
  languages.push(LANGUAGE_SHORT_ENGLISH);
33
33
  }
34
34
 
35
+ const url = makeUrl('exerciseinfo', {
36
+ query: {
37
+ name__search: name,
38
+ language__code: languages.join(','),
39
+ limit: 50,
40
+ }
41
+ });
35
42
 
36
- const url = makeUrl(EXERCISE_SEARCH_PATH, { query: { term: name, language: languages.join(',') } });
37
-
38
- const { data } = await axios.get<ExerciseSearchType>(url);
39
- return data.suggestions;
43
+ try {
44
+ const { data } = await axios.get<ResponseType<Exercise>>(url);
45
+
46
+ if (!data || !data.results || !Array.isArray(data.results)) {
47
+ return [];
48
+ }
49
+
50
+ const adapter = new ExerciseAdapter();
51
+ return data.results.map((item: unknown) => adapter.fromJson(item));
52
+ } catch {
53
+ return [];
54
+ }
40
55
  };
41
56
 
42
57
 
@@ -1,35 +1,9 @@
1
- import { Exercise } from "components/Exercises/models/exercise";
2
-
1
+ /*
2
+ * Generic paginated response from the wger API
3
+ */
3
4
  export interface ResponseType<T> {
4
5
  count: number,
5
6
  next: number | null,
6
7
  previous: number | null,
7
8
  results: T[]
8
9
  }
9
-
10
- export interface ExerciseSearchResponse {
11
- value: string,
12
- data: {
13
- id: number,
14
- base_id: number,
15
- name: string,
16
- category: string,
17
- image: string | null,
18
- image_thumbnail: string | null,
19
- },
20
- exercise?: Exercise
21
- }
22
-
23
- export interface ExerciseSearchType {
24
- suggestions: ExerciseSearchResponse[];
25
- }
26
-
27
- export interface IngredientSearchResponse {
28
- value: string,
29
- data: {
30
- id: number,
31
- name: string,
32
- image: string | null,
33
- image_thumbnail: string | null,
34
- }
35
- }
@@ -1,23 +1,48 @@
1
- export const searchResponse = [
2
- {
3
- "value": "Crunches an Negativbank",
4
- "data": {
5
- "id": 1149,
6
- "base_id": 998,
7
- "name": "Crunches an Negativbank",
8
- "category": "Bauch",
9
- "image": null,
10
- "image_thumbnail": null
11
- }
12
- }, {
13
- "value": "Crunches am Seil",
14
- "data": {
15
- "id": 1213,
16
- "base_id": 979,
17
- "name": "Crunches am Seil",
18
- "category": "Brust",
19
- "image": null,
20
- "image_thumbnail": null
21
- }
22
- }
1
+ import { Exercise } from "components/Exercises/models/exercise";
2
+ import { Category } from "components/Exercises/models/category";
3
+ import { Translation } from "components/Exercises/models/translation";
4
+
5
+ export const searchResponse: Exercise[] = [
6
+ new Exercise(
7
+ 998, // id
8
+ "uuid-998", // uuid
9
+ new Category(8, "Bauch"), // category
10
+ [], // equipment
11
+ [], // muscles
12
+ [], // musclesSecondary
13
+ [], // images
14
+ null, // variationId
15
+ [
16
+ new Translation(
17
+ 1149, // id
18
+ "uuid-1149", // uuid
19
+ "Crunches an Negativbank", // name
20
+ "", // description
21
+ 1 // language (German)
22
+ )
23
+ ], // translations
24
+ [], // videos
25
+ [] // authors
26
+ ),
27
+ new Exercise(
28
+ 979, // id
29
+ "uuid-979", // uuid
30
+ new Category(11, "Brust"), // category
31
+ [], // equipment
32
+ [], // muscles
33
+ [], // musclesSecondary
34
+ [], // images
35
+ null, // variationId
36
+ [
37
+ new Translation(
38
+ 1213, // id
39
+ "uuid-1213", // uuid
40
+ "Crunches am Seil", // name
41
+ "", // description
42
+ 1 // language (German)
43
+ )
44
+ ], // translations
45
+ [], // videos
46
+ [] // authors
47
+ )
23
48
  ];