@wger-project/react-components 25.12.5 → 26.2.26
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.
- package/build/assets/ajax-loader.gif +0 -0
- package/build/assets/index.css +1 -1
- package/build/assets/slick.svg +14 -0
- package/build/locales/ar/translation.json +347 -236
- package/build/locales/cs/translation.json +257 -253
- package/build/locales/de/translation.json +13 -3
- package/build/locales/en/translation.json +4 -1
- package/build/locales/es/translation.json +26 -4
- package/build/locales/fr/translation.json +10 -1
- package/build/locales/hi/translation.json +4 -1
- package/build/locales/hr/translation.json +15 -3
- package/build/locales/mk/translation.json +138 -0
- package/build/locales/nl/translation.json +118 -11
- package/build/locales/pt/translation.json +338 -338
- package/build/locales/pt_BR/translation.json +15 -3
- package/build/locales/ru/translation.json +43 -3
- package/build/locales/ta/translation.json +1 -1
- package/build/locales/uk/translation.json +13 -1
- package/build/locales/zh_Hans/translation.json +25 -1
- package/build/locales/zh_Hant/translation.json +255 -243
- package/build/main.js +170 -170
- package/build/main.js.map +1 -1
- package/package.json +15 -13
- package/src/components/BodyWeight/TableDashboard/TableDashboard.tsx +4 -6
- package/src/components/Calendar/Components/CalendarComponent.test.tsx +18 -22
- package/src/components/Calendar/Components/CalendarComponent.tsx +11 -8
- package/src/components/Calendar/Components/CalendarHeader.tsx +3 -3
- package/src/components/Calendar/Components/Entries.tsx +8 -3
- package/src/components/Dashboard/CalendarCard.tsx +16 -0
- package/src/components/Dashboard/ConfigurableDashboard.test.ts +129 -0
- package/src/components/Dashboard/ConfigurableDashboard.tsx +352 -89
- package/src/components/Dashboard/DashboardCard.tsx +3 -2
- package/src/components/Dashboard/MeasurementCard.test.tsx +75 -0
- package/src/components/Dashboard/MeasurementCard.tsx +101 -0
- package/src/components/Dashboard/RoutineCard.tsx +1 -1
- package/src/components/Dashboard/TrophiesCard.test.tsx +63 -0
- package/src/components/Dashboard/TrophiesCard.tsx +84 -0
- package/src/components/Dashboard/WeightCard.test.tsx +0 -10
- package/src/components/Measurements/Screens/MeasurementCategoryOverview.tsx +1 -1
- package/src/components/Measurements/models/Category.ts +13 -2
- package/src/components/Measurements/models/Entry.ts +13 -2
- package/src/components/Trophies/components/TrophiesDetail.test.tsx +34 -0
- package/src/components/Trophies/components/TrophiesDetail.tsx +88 -0
- package/src/components/Trophies/models/trophy.test.ts +33 -0
- package/src/components/Trophies/models/trophy.ts +75 -0
- package/src/components/Trophies/models/userTrophy.test.ts +38 -0
- package/src/components/Trophies/models/userTrophy.ts +67 -0
- package/src/components/Trophies/models/userTrophyProgression.test.ts +43 -0
- package/src/components/Trophies/models/userTrophyProgression.ts +68 -0
- package/src/components/Trophies/queries/trophies.ts +31 -0
- package/src/components/Trophies/services/trophies.ts +22 -0
- package/src/components/Trophies/services/userTrophies.ts +33 -0
- package/src/components/Trophies/services/userTrophyProgression.ts +16 -0
- package/src/components/WorkoutRoutines/Detail/RoutineDetail.tsx +1 -1
- package/src/components/WorkoutRoutines/Detail/TemplateDetail.tsx +1 -1
- package/src/components/WorkoutRoutines/models/Routine.test.ts +17 -0
- package/src/components/WorkoutRoutines/models/Routine.ts +20 -3
- package/src/components/WorkoutRoutines/widgets/RoutineDetailsCard.tsx +1 -1
- package/src/components/index.ts +0 -2
- package/src/pages/Calendar/index.tsx +2 -2
- package/src/pages/WeightOverview/index.tsx +1 -1
- package/src/routes.tsx +6 -1
- package/src/services/measurements.ts +10 -17
- package/src/tests/trophies/trophiesTestData.ts +80 -0
- package/src/utils/consts.ts +18 -3
- package/src/utils/url.test.ts +32 -1
- package/src/utils/url.ts +24 -3
- package/src/components/Carousel/carousel.module.css +0 -43
- package/src/components/Carousel/carousel.module.css.map +0 -1
- package/src/components/Carousel/carousel.module.scss +0 -46
- package/src/components/Carousel/index.tsx +0 -66
- package/src/components/Dashboard/GoalCard.tsx +0 -71
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ApiTrophyType, Trophy } from "components/Trophies/models/trophy";
|
|
2
|
+
import { Adapter } from "utils/Adapter";
|
|
3
|
+
|
|
4
|
+
export interface ApiUserTrophyType {
|
|
5
|
+
id: number,
|
|
6
|
+
trophy: ApiTrophyType,
|
|
7
|
+
earned_at: string,
|
|
8
|
+
progress: number,
|
|
9
|
+
"is_notified": boolean,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type UserTrophyConstructorParams = {
|
|
13
|
+
id: number;
|
|
14
|
+
trophy: Trophy;
|
|
15
|
+
earnedAt: Date;
|
|
16
|
+
progress: number;
|
|
17
|
+
isNotified: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* A list of trophies earned by a user, along with their progress.
|
|
22
|
+
*/
|
|
23
|
+
export class UserTrophy {
|
|
24
|
+
|
|
25
|
+
public id: number;
|
|
26
|
+
public trophy: Trophy;
|
|
27
|
+
public earnedAt: Date;
|
|
28
|
+
public progress: number;
|
|
29
|
+
public isNotified: boolean;
|
|
30
|
+
|
|
31
|
+
constructor(params: UserTrophyConstructorParams) {
|
|
32
|
+
this.id = params.id;
|
|
33
|
+
this.trophy = params.trophy;
|
|
34
|
+
this.earnedAt = params.earnedAt;
|
|
35
|
+
this.progress = params.progress;
|
|
36
|
+
this.isNotified = params.isNotified;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static fromJson(json: ApiUserTrophyType): UserTrophy {
|
|
40
|
+
return adapter.fromJson(json);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
toJson() {
|
|
44
|
+
return adapter.toJson(this);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class UserTrophyAdapter implements Adapter<UserTrophy> {
|
|
49
|
+
fromJson(item: ApiUserTrophyType) {
|
|
50
|
+
return new UserTrophy({
|
|
51
|
+
id: item.id,
|
|
52
|
+
trophy: Trophy.fromJson(item.trophy),
|
|
53
|
+
earnedAt: new Date(item.earned_at),
|
|
54
|
+
progress: item.progress,
|
|
55
|
+
isNotified: item.is_notified,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
toJson(trophy: UserTrophy) {
|
|
60
|
+
return {
|
|
61
|
+
id: trophy.id,
|
|
62
|
+
"is_notified": trophy.isNotified,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const adapter = new UserTrophyAdapter();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { trophyType } from "components/Trophies/models/trophy";
|
|
2
|
+
import { UserTrophyProgression } from "components/Trophies/models/userTrophyProgression";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
describe('Test the UserTrophyProgression model', () => {
|
|
6
|
+
|
|
7
|
+
test('correctly creates an object from the API response', () => {
|
|
8
|
+
// Arrange
|
|
9
|
+
const apiResponse = {
|
|
10
|
+
trophy: {
|
|
11
|
+
id: 1,
|
|
12
|
+
uuid: "5362e55b-eaf1-4e34-9ef8-661538a3bdd9",
|
|
13
|
+
name: "Beginner",
|
|
14
|
+
description: "Complete your first workout",
|
|
15
|
+
image: "http://localhost:8000/static/trophies/count/5362e55b-eaf1-4e34-9ef8-661538a3bdd9.png",
|
|
16
|
+
"trophy_type": "count" as trophyType,
|
|
17
|
+
"is_hidden": false,
|
|
18
|
+
"is_progressive": false,
|
|
19
|
+
order: 1
|
|
20
|
+
},
|
|
21
|
+
"is_earned": true,
|
|
22
|
+
"earned_at": "2025-12-19T13:48:07.513138+01:00",
|
|
23
|
+
progress: 100.0,
|
|
24
|
+
"current_value": null,
|
|
25
|
+
"target_value": null,
|
|
26
|
+
"progress_display": null
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Act
|
|
30
|
+
const userTrophyProgression = UserTrophyProgression.fromJson(apiResponse);
|
|
31
|
+
|
|
32
|
+
// Assert
|
|
33
|
+
expect(userTrophyProgression.isEarned).toBe(true);
|
|
34
|
+
expect(userTrophyProgression.earnedAt).toStrictEqual(new Date("2025-12-19T13:48:07.513138+01:00"));
|
|
35
|
+
expect(userTrophyProgression.progress).toBe(100.0);
|
|
36
|
+
expect(userTrophyProgression.currentValue).toBe(null);
|
|
37
|
+
expect(userTrophyProgression.targetValue).toBe(null);
|
|
38
|
+
expect(userTrophyProgression.progressDisplay).toBe(null);
|
|
39
|
+
|
|
40
|
+
expect(userTrophyProgression.trophy.uuid).toBe("5362e55b-eaf1-4e34-9ef8-661538a3bdd9");
|
|
41
|
+
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ApiTrophyType, Trophy } from "components/Trophies/models/trophy";
|
|
2
|
+
import { Adapter } from "utils/Adapter";
|
|
3
|
+
|
|
4
|
+
export interface ApiUserTrophyType {
|
|
5
|
+
trophy: ApiTrophyType,
|
|
6
|
+
is_earned: boolean
|
|
7
|
+
earned_at: string | null,
|
|
8
|
+
progress: number,
|
|
9
|
+
current_value: number | null,
|
|
10
|
+
target_value: number | null,
|
|
11
|
+
progress_display: string | null,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type UserTrophyConstructorParams = {
|
|
15
|
+
trophy: Trophy;
|
|
16
|
+
isEarned: boolean;
|
|
17
|
+
earnedAt: Date | null;
|
|
18
|
+
progress: number;
|
|
19
|
+
currentValue: number | null;
|
|
20
|
+
targetValue: number | null;
|
|
21
|
+
progressDisplay: string | null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export class UserTrophyProgression {
|
|
25
|
+
|
|
26
|
+
public trophy: Trophy;
|
|
27
|
+
public earnedAt: Date | null;
|
|
28
|
+
public isEarned: boolean;
|
|
29
|
+
public progress: number;
|
|
30
|
+
public currentValue: number | null;
|
|
31
|
+
public targetValue: number | null;
|
|
32
|
+
public progressDisplay: string | null;
|
|
33
|
+
|
|
34
|
+
constructor(params: UserTrophyConstructorParams) {
|
|
35
|
+
this.trophy = params.trophy;
|
|
36
|
+
this.earnedAt = params.earnedAt;
|
|
37
|
+
this.isEarned = params.isEarned;
|
|
38
|
+
this.progress = params.progress;
|
|
39
|
+
this.currentValue = params.currentValue;
|
|
40
|
+
this.targetValue = params.targetValue;
|
|
41
|
+
this.progressDisplay = params.progressDisplay;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static fromJson(json: ApiUserTrophyType): UserTrophyProgression {
|
|
45
|
+
return adapter.fromJson(json);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class UserTrophyAdapter implements Adapter<UserTrophyProgression> {
|
|
50
|
+
fromJson(item: ApiUserTrophyType) {
|
|
51
|
+
return new UserTrophyProgression({
|
|
52
|
+
trophy: Trophy.fromJson(item.trophy),
|
|
53
|
+
earnedAt: item.earned_at !== null ? new Date(item.earned_at) : null,
|
|
54
|
+
isEarned: item.is_earned,
|
|
55
|
+
progress: item.progress,
|
|
56
|
+
currentValue: item.current_value,
|
|
57
|
+
targetValue: item.target_value,
|
|
58
|
+
progressDisplay: item.progress_display,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
toJson(item: UserTrophyProgression) {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
const adapter = new UserTrophyAdapter();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { getTrophies } from "components/Trophies/services/trophies";
|
|
3
|
+
import { getUserTrophies, setTrophyAsNotified } from "components/Trophies/services/userTrophies";
|
|
4
|
+
import { getUserTrophyProgression } from "components/Trophies/services/userTrophyProgression";
|
|
5
|
+
import { QueryKey } from "utils/consts";
|
|
6
|
+
|
|
7
|
+
export const useTrophiesQuery = () => useQuery({
|
|
8
|
+
queryFn: () => getTrophies(),
|
|
9
|
+
queryKey: [QueryKey.TROPHIES],
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const useUserTrophiesQuery = () => useQuery({
|
|
13
|
+
queryFn: () => getUserTrophies(),
|
|
14
|
+
queryKey: [QueryKey.USER_TROPHIES],
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const useSetTrophyAsNotifiedQuery = () => {
|
|
18
|
+
const queryClient = useQueryClient();
|
|
19
|
+
|
|
20
|
+
return useMutation({
|
|
21
|
+
mutationFn: (trophyId: number) => setTrophyAsNotified(trophyId),
|
|
22
|
+
onSuccess: () => queryClient.invalidateQueries({
|
|
23
|
+
queryKey: [QueryKey.USER_TROPHIES]
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const useUserTrophyProgressionQuery = () => useQuery({
|
|
29
|
+
queryFn: () => getUserTrophyProgression(),
|
|
30
|
+
queryKey: [QueryKey.USER_TROPHY_PROGRESSION],
|
|
31
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Trophy } from "components/Trophies/models/trophy";
|
|
2
|
+
import { API_MAX_PAGE_SIZE, ApiPath } from "utils/consts";
|
|
3
|
+
import { fetchPaginated } from "utils/requests";
|
|
4
|
+
import { makeHeader, makeUrl } from "utils/url";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export const getTrophies = async (): Promise<Trophy[]> => {
|
|
8
|
+
|
|
9
|
+
const url = makeUrl(
|
|
10
|
+
ApiPath.API_TROPHIES_PATH,
|
|
11
|
+
{ query: { limit: API_MAX_PAGE_SIZE } }
|
|
12
|
+
);
|
|
13
|
+
const out: Trophy[] = [];
|
|
14
|
+
|
|
15
|
+
for await (const page of fetchPaginated(url, makeHeader())) {
|
|
16
|
+
for (const logData of page) {
|
|
17
|
+
out.push(Trophy.fromJson(logData));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
};
|
|
22
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { UserTrophy } from "components/Trophies/models/userTrophy";
|
|
3
|
+
import { API_MAX_PAGE_SIZE, ApiPath } from "utils/consts";
|
|
4
|
+
import { fetchPaginated } from "utils/requests";
|
|
5
|
+
import { makeHeader, makeUrl } from "utils/url";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export const getUserTrophies = async (): Promise<UserTrophy[]> => {
|
|
9
|
+
|
|
10
|
+
const url = makeUrl(
|
|
11
|
+
ApiPath.API_USER_TROPHIES_PATH,
|
|
12
|
+
{ query: { limit: API_MAX_PAGE_SIZE } }
|
|
13
|
+
);
|
|
14
|
+
const out: UserTrophy[] = [];
|
|
15
|
+
|
|
16
|
+
for await (const page of fetchPaginated(url, makeHeader())) {
|
|
17
|
+
for (const logData of page) {
|
|
18
|
+
out.push(UserTrophy.fromJson(logData));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
export const setTrophyAsNotified = async (trophyId: number): Promise<void> => {
|
|
26
|
+
|
|
27
|
+
await axios.patch(
|
|
28
|
+
makeUrl(ApiPath.API_USER_TROPHIES_PATH, { id: trophyId }),
|
|
29
|
+
{ id: trophyId, "is_notified": true },
|
|
30
|
+
{ headers: makeHeader() }
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { ApiUserTrophyType, UserTrophyProgression } from "components/Trophies/models/userTrophyProgression";
|
|
3
|
+
import { ApiPath } from "utils/consts";
|
|
4
|
+
import { makeHeader, makeUrl } from "utils/url";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export const getUserTrophyProgression = async (): Promise<UserTrophyProgression[]> => {
|
|
8
|
+
|
|
9
|
+
const { data: trophyData } = await axios.get(
|
|
10
|
+
makeUrl(ApiPath.API_USER_TROPHY_PROGRESSION_PATH),
|
|
11
|
+
{ headers: makeHeader() }
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
return trophyData.map((item: ApiUserTrophyType) => UserTrophyProgression.fromJson(item));
|
|
15
|
+
};
|
|
16
|
+
|
|
@@ -51,7 +51,7 @@ export const RoutineDetail = () => {
|
|
|
51
51
|
href={makeLink(WgerLink.ROUTINE_COPY, i18n.language, { id: routineId })}
|
|
52
52
|
variant={"contained"}
|
|
53
53
|
>{t('routines.copyAndUseTemplate')}</Button>}
|
|
54
|
-
{routine!.
|
|
54
|
+
{routine!.dayDataCurrentIterationFiltered.map((dayData) =>
|
|
55
55
|
<DayDetailsCard
|
|
56
56
|
routineId={routineId}
|
|
57
57
|
dayData={dayData}
|
|
@@ -48,7 +48,7 @@ export const TemplateDetail = () => {
|
|
|
48
48
|
variant={"contained"}
|
|
49
49
|
>{t('routines.copyAndUseTemplate')}</Button>
|
|
50
50
|
|
|
51
|
-
{routine!.
|
|
51
|
+
{routine!.dayDataCurrentIterationFiltered.map((dayData) =>
|
|
52
52
|
<DayDetailsCard
|
|
53
53
|
dayData={dayData}
|
|
54
54
|
routineId={routineId}
|
|
@@ -110,4 +110,21 @@ describe('Routine model tests', () => {
|
|
|
110
110
|
// Assert
|
|
111
111
|
expect(routine.dayDataCurrentIteration).toEqual(testRoutineDayData1);
|
|
112
112
|
});
|
|
113
|
+
|
|
114
|
+
test('correctly filters out null days', () => {
|
|
115
|
+
|
|
116
|
+
// Arrange
|
|
117
|
+
routine.dayData = [
|
|
118
|
+
...testRoutineDayData1,
|
|
119
|
+
...testRoutineDayData1,
|
|
120
|
+
...testRoutineDayData1,
|
|
121
|
+
];
|
|
122
|
+
routine.dayData[0].date = new Date('2026-01-01');
|
|
123
|
+
routine.dayData[1].date = new Date('2026-01-02');
|
|
124
|
+
routine.dayData[2].date = new Date('2026-01-03');
|
|
125
|
+
|
|
126
|
+
// Assert
|
|
127
|
+
expect(routine.dayDataCurrentIteration.length).toEqual(3);
|
|
128
|
+
expect(routine.dayDataCurrentIterationFiltered).toEqual(testRoutineDayData1);
|
|
129
|
+
});
|
|
113
130
|
});
|
|
@@ -62,12 +62,29 @@ export class Routine {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/*
|
|
65
|
-
* Filter out dayData entries with null days
|
|
65
|
+
* Filter out dayData entries with null days as well as duplicated days from
|
|
66
|
+
* the "fixed weekly schedule" toggle.
|
|
66
67
|
*/
|
|
67
|
-
get
|
|
68
|
-
|
|
68
|
+
get dayDataCurrentIterationFiltered() {
|
|
69
|
+
const sorted = this.dayDataCurrentIteration
|
|
69
70
|
.filter((dayData) => dayData.day !== null)
|
|
70
71
|
.sort((a, b) => a.day!.order - b.day!.order);
|
|
72
|
+
|
|
73
|
+
// Filter out entries where the day is the same as the previous one. This is
|
|
74
|
+
// necessary because if the user has the "Fixed weekly schedule" option enabled,
|
|
75
|
+
// there would be multiple entries for the same day.
|
|
76
|
+
const unique: RoutineDayData[] = [];
|
|
77
|
+
for (const dd of sorted) {
|
|
78
|
+
if (unique.length === 0 || unique[unique.length - 1].day!.id !== dd.day!.id) {
|
|
79
|
+
unique.push(dd);
|
|
80
|
+
} else {
|
|
81
|
+
// If the day id is the same as the previous entry, replace it with the current one
|
|
82
|
+
// so the last occurrence is kept.
|
|
83
|
+
unique[unique.length - 1] = dd;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return unique;
|
|
71
88
|
}
|
|
72
89
|
|
|
73
90
|
get groupedDayDataByIteration() {
|
|
@@ -53,7 +53,7 @@ export const RoutineDetailsCard = () => {
|
|
|
53
53
|
</Typography>
|
|
54
54
|
}
|
|
55
55
|
<Stack spacing={2} sx={{ mt: 2 }}>
|
|
56
|
-
{routineQuery.data!.
|
|
56
|
+
{routineQuery.data!.dayDataCurrentIterationFiltered.map((dayData, index) =>
|
|
57
57
|
<DayDetailsCard routineId={routineId} dayData={dayData} key={`dayDetails-${index}`} />
|
|
58
58
|
)}
|
|
59
59
|
</Stack>
|
package/src/components/index.ts
CHANGED
package/src/routes.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { BmiCalculator } from "components/Nutrition/components/BmiCalculator";
|
|
|
6
6
|
import { NutritionDiaryOverview } from "components/Nutrition/components/NutritionDiaryOverview";
|
|
7
7
|
import { PlanDetail } from "components/Nutrition/components/PlanDetail";
|
|
8
8
|
import { PlansOverview } from "components/Nutrition/components/PlansOverview";
|
|
9
|
+
import { TrophiesDetail } from "components/Trophies/components/TrophiesDetail";
|
|
9
10
|
import { RoutineAdd } from "components/WorkoutRoutines/Detail/RoutineAdd";
|
|
10
11
|
import { RoutineDetail } from "components/WorkoutRoutines/Detail/RoutineDetail";
|
|
11
12
|
import { RoutineDetailsTable } from "components/WorkoutRoutines/Detail/RoutineDetailsTable";
|
|
@@ -123,10 +124,14 @@ export const WgerRoutes = () => {
|
|
|
123
124
|
<Route index element={<ConfigurableDashboard />} />
|
|
124
125
|
<Route path="" element={<ConfigurableDashboard />} />
|
|
125
126
|
</Route>
|
|
127
|
+
<Route path="trophies">
|
|
128
|
+
<Route index element={<TrophiesDetail />} />
|
|
129
|
+
<Route path="" element={<TrophiesDetail />} />
|
|
130
|
+
</Route>
|
|
126
131
|
</Route>
|
|
127
132
|
<Route path="/" element={<ConfigurableDashboard />} />
|
|
128
133
|
|
|
129
|
-
{/* This route matches when no other route
|
|
134
|
+
{/* This route matches when no other route matches, so a 404 */}
|
|
130
135
|
<Route
|
|
131
136
|
path="*"
|
|
132
137
|
element={
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
-
import { MeasurementCategory
|
|
3
|
-
import { MeasurementEntry
|
|
2
|
+
import { MeasurementCategory } from "components/Measurements/models/Category";
|
|
3
|
+
import { MeasurementEntry } from "components/Measurements/models/Entry";
|
|
4
4
|
import { ApiMeasurementCategoryType } from 'types';
|
|
5
5
|
import { API_MAX_PAGE_SIZE } from "utils/consts";
|
|
6
6
|
import { dateToYYYYMMDD } from "utils/date";
|
|
@@ -18,8 +18,6 @@ export type MeasurementQueryOptions = {
|
|
|
18
18
|
export const getMeasurementCategories = async (options?: MeasurementQueryOptions): Promise<MeasurementCategory[]> => {
|
|
19
19
|
const { filtersetQueryCategories = {}, filtersetQueryEntries = {} } = options || {};
|
|
20
20
|
|
|
21
|
-
const adapter = new MeasurementCategoryAdapter();
|
|
22
|
-
const entryAdapter = new MeasurementEntryAdapter();
|
|
23
21
|
const categories: MeasurementCategory[] = [];
|
|
24
22
|
const categoryUrl = makeUrl(API_MEASUREMENTS_CATEGORY_PATH, {
|
|
25
23
|
query: {
|
|
@@ -30,7 +28,7 @@ export const getMeasurementCategories = async (options?: MeasurementQueryOptions
|
|
|
30
28
|
|
|
31
29
|
for await (const page of fetchPaginated(categoryUrl, makeHeader())) {
|
|
32
30
|
for (const catData of page) {
|
|
33
|
-
categories.push(
|
|
31
|
+
categories.push(MeasurementCategory.fromJson(catData));
|
|
34
32
|
}
|
|
35
33
|
}
|
|
36
34
|
|
|
@@ -48,7 +46,7 @@ export const getMeasurementCategories = async (options?: MeasurementQueryOptions
|
|
|
48
46
|
// Collect all pages of entries
|
|
49
47
|
for await (const page of fetchPaginated(url, makeHeader())) {
|
|
50
48
|
for (const entries of page) {
|
|
51
|
-
out.push(
|
|
49
|
+
out.push(MeasurementEntry.fromJson(entries));
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
52
|
return out;
|
|
@@ -73,15 +71,14 @@ export const getMeasurementCategory = async (id: number): Promise<MeasurementCat
|
|
|
73
71
|
{ headers: makeHeader() },
|
|
74
72
|
);
|
|
75
73
|
|
|
76
|
-
const category =
|
|
77
|
-
const adapter = new MeasurementEntryAdapter();
|
|
74
|
+
const category = MeasurementCategory.fromJson(receivedCategories);
|
|
78
75
|
const measurements: MeasurementEntry[] = [];
|
|
79
76
|
const url = makeUrl(API_MEASUREMENTS_ENTRY_PATH, { query: { category: category.id } });
|
|
80
77
|
|
|
81
78
|
// Collect all pages of entries
|
|
82
79
|
for await (const page of fetchPaginated(url, makeHeader())) {
|
|
83
80
|
for (const entries of page) {
|
|
84
|
-
measurements.push(
|
|
81
|
+
measurements.push(MeasurementEntry.fromJson(entries));
|
|
85
82
|
}
|
|
86
83
|
}
|
|
87
84
|
|
|
@@ -105,8 +102,7 @@ export const addMeasurementCategory = async (data: AddMeasurementCategoryParams)
|
|
|
105
102
|
{ headers: makeHeader() }
|
|
106
103
|
);
|
|
107
104
|
|
|
108
|
-
|
|
109
|
-
return adapter.fromJson(response.data);
|
|
105
|
+
return MeasurementCategory.fromJson(response.data);
|
|
110
106
|
};
|
|
111
107
|
|
|
112
108
|
export interface editMeasurementCategoryParams {
|
|
@@ -125,8 +121,7 @@ export const editMeasurementCategory = async (data: editMeasurementCategoryParam
|
|
|
125
121
|
{ headers: makeHeader() }
|
|
126
122
|
);
|
|
127
123
|
|
|
128
|
-
|
|
129
|
-
return adapter.fromJson(response.data);
|
|
124
|
+
return MeasurementCategory.fromJson(response.data);
|
|
130
125
|
};
|
|
131
126
|
|
|
132
127
|
export const deleteMeasurementCategory = async (id: number): Promise<void> => {
|
|
@@ -157,8 +152,7 @@ export const editMeasurementEntry = async (data: editMeasurementParams): Promise
|
|
|
157
152
|
{ headers: makeHeader() }
|
|
158
153
|
);
|
|
159
154
|
|
|
160
|
-
|
|
161
|
-
return adapter.fromJson(response.data);
|
|
155
|
+
return MeasurementEntry.fromJson(response.data);
|
|
162
156
|
};
|
|
163
157
|
|
|
164
158
|
export interface AddMeasurementParams {
|
|
@@ -181,6 +175,5 @@ export const addMeasurementEntry = async (data: AddMeasurementParams): Promise<M
|
|
|
181
175
|
{ headers: makeHeader() }
|
|
182
176
|
);
|
|
183
177
|
|
|
184
|
-
|
|
185
|
-
return adapter.fromJson(response.data);
|
|
178
|
+
return MeasurementEntry.fromJson(response.data);
|
|
186
179
|
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Trophy } from "components/Trophies/models/trophy";
|
|
2
|
+
import { UserTrophy } from "components/Trophies/models/userTrophy";
|
|
3
|
+
import { UserTrophyProgression } from "components/Trophies/models/userTrophyProgression";
|
|
4
|
+
|
|
5
|
+
export const testTrophies = () => {
|
|
6
|
+
return [
|
|
7
|
+
new Trophy({
|
|
8
|
+
id: 123,
|
|
9
|
+
type: 'other',
|
|
10
|
+
isHidden: false,
|
|
11
|
+
uuid: 'trophy-123',
|
|
12
|
+
name: 'Beginner',
|
|
13
|
+
description: 'Complete your first workout',
|
|
14
|
+
image: 'https://example.com/images/beginner.png',
|
|
15
|
+
isProgressive: false,
|
|
16
|
+
}),
|
|
17
|
+
new Trophy({
|
|
18
|
+
id: 456,
|
|
19
|
+
type: 'count',
|
|
20
|
+
isHidden: false,
|
|
21
|
+
uuid: 'trophy-456',
|
|
22
|
+
name: 'Unstoppable',
|
|
23
|
+
description: 'Maintain a 30-day workout streak',
|
|
24
|
+
image: 'https://example.com/images/unstoppable.png',
|
|
25
|
+
isProgressive: true,
|
|
26
|
+
}),
|
|
27
|
+
new Trophy({
|
|
28
|
+
id: 789,
|
|
29
|
+
type: 'other',
|
|
30
|
+
isHidden: true,
|
|
31
|
+
uuid: 'trophy-789',
|
|
32
|
+
name: 'Secret Trophy',
|
|
33
|
+
description: 'This is a super secret trophy',
|
|
34
|
+
image: 'https://example.com/images/secret.png',
|
|
35
|
+
isProgressive: false,
|
|
36
|
+
})
|
|
37
|
+
];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const testUserProgressionTrophies = () => {
|
|
41
|
+
return [
|
|
42
|
+
new UserTrophyProgression({
|
|
43
|
+
trophy: testTrophies()[0],
|
|
44
|
+
isEarned: true,
|
|
45
|
+
earnedAt: new Date('2025-12-19T10:00:00Z'),
|
|
46
|
+
progress: 100,
|
|
47
|
+
currentValue: null,
|
|
48
|
+
targetValue: null,
|
|
49
|
+
progressDisplay: null,
|
|
50
|
+
}),
|
|
51
|
+
new UserTrophyProgression({
|
|
52
|
+
trophy: testTrophies()[1],
|
|
53
|
+
isEarned: false,
|
|
54
|
+
earnedAt: new Date('2025-12-19T10:00:00Z'),
|
|
55
|
+
progress: 13.333333333333334,
|
|
56
|
+
currentValue: 4,
|
|
57
|
+
targetValue: 30,
|
|
58
|
+
progressDisplay: '4/30',
|
|
59
|
+
}),
|
|
60
|
+
];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const testUserTrophies = () => {
|
|
64
|
+
return [
|
|
65
|
+
new UserTrophy({
|
|
66
|
+
id: 123,
|
|
67
|
+
isNotified: true,
|
|
68
|
+
trophy: testTrophies()[0],
|
|
69
|
+
earnedAt: new Date('2025-12-19T10:00:00Z'),
|
|
70
|
+
progress: 100,
|
|
71
|
+
}),
|
|
72
|
+
new UserTrophy({
|
|
73
|
+
id: 456,
|
|
74
|
+
isNotified: true,
|
|
75
|
+
trophy: testTrophies()[1],
|
|
76
|
+
earnedAt: new Date('2025-12-19T10:00:00Z'),
|
|
77
|
+
progress: 100,
|
|
78
|
+
}),
|
|
79
|
+
];
|
|
80
|
+
};
|
package/src/utils/consts.ts
CHANGED
|
@@ -35,6 +35,8 @@ export const QUERY_MEASUREMENTS_CATEGORIES = 'measurements-categories';
|
|
|
35
35
|
* These don't have any meaning, they just need to be globally unique
|
|
36
36
|
*/
|
|
37
37
|
export enum QueryKey {
|
|
38
|
+
|
|
39
|
+
// Routines
|
|
38
40
|
ROUTINE_OVERVIEW = 'routine-overview',
|
|
39
41
|
ROUTINE_DETAIL = 'routine-detail',
|
|
40
42
|
SESSION_SEARCH = 'session-search',
|
|
@@ -46,20 +48,23 @@ export enum QueryKey {
|
|
|
46
48
|
ROUTINES_SHALLOW = 'routines-shallow',
|
|
47
49
|
PRIVATE_TEMPLATES = 'private-templates',
|
|
48
50
|
PUBLIC_TEMPLATES = 'public-templates',
|
|
51
|
+
ROUTINE_WEIGHT_UNITS = 'weight-units',
|
|
52
|
+
ROUTINE_REP_UNITS = 'rep-units',
|
|
49
53
|
|
|
54
|
+
// Nutrition
|
|
50
55
|
NUTRITIONAL_PLANS = 'nutritional-plans',
|
|
51
56
|
NUTRITIONAL_PLAN = 'nutritional-plan',
|
|
52
57
|
NUTRITIONAL_PLAN_DIARY = 'nutritional-plan-diary',
|
|
53
58
|
NUTRITIONAL_PLAN_LAST = 'nutritional-plan-last',
|
|
54
59
|
INGREDIENT = 'ingredient',
|
|
55
60
|
|
|
61
|
+
// Body weight
|
|
56
62
|
BODY_WEIGHT = 'body-weight',
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
ROUTINE_REP_UNITS = 'rep-units',
|
|
60
|
-
|
|
64
|
+
// Profile
|
|
61
65
|
QUERY_PROFILE = 'profile',
|
|
62
66
|
|
|
67
|
+
// Exercises
|
|
63
68
|
EXERCISES = 'exercises',
|
|
64
69
|
EXERCISE_VARIATIONS = 'variations',
|
|
65
70
|
EXERCISE_DETAIL = 'detail',
|
|
@@ -68,6 +73,11 @@ export enum QueryKey {
|
|
|
68
73
|
EQUIPMENT = 'equipment',
|
|
69
74
|
MUSCLES = 'muscles',
|
|
70
75
|
QUERY_NOTES = 'notes',
|
|
76
|
+
|
|
77
|
+
// Trophies
|
|
78
|
+
TROPHIES = 'trophies',
|
|
79
|
+
USER_TROPHIES = 'user-trophies',
|
|
80
|
+
USER_TROPHY_PROGRESSION = 'user-trophy-progression',
|
|
71
81
|
}
|
|
72
82
|
|
|
73
83
|
/*
|
|
@@ -106,6 +116,11 @@ export enum ApiPath {
|
|
|
106
116
|
|
|
107
117
|
// Profile
|
|
108
118
|
API_PROFILE_PATH = 'userprofile',
|
|
119
|
+
|
|
120
|
+
// Trophies
|
|
121
|
+
API_TROPHIES_PATH = 'trophy',
|
|
122
|
+
API_USER_TROPHIES_PATH = 'user-trophy',
|
|
123
|
+
API_USER_TROPHY_PROGRESSION_PATH = 'trophy/progress',
|
|
109
124
|
}
|
|
110
125
|
|
|
111
126
|
|