@wger-project/react-components 25.11.22 → 26.1.18
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/de/translation.json +25 -6
- package/build/locales/en/translation.json +12 -0
- package/build/locales/es/translation.json +22 -3
- package/build/locales/fr/translation.json +10 -1
- package/build/locales/hi/translation.json +8 -0
- package/build/locales/nl/translation.json +350 -239
- package/build/locales/pt_BR/translation.json +349 -255
- package/build/locales/ru/translation.json +39 -3
- package/build/locales/uk/translation.json +10 -1
- package/build/main.js +170 -166
- package/build/main.js.map +1 -1
- package/package.json +8 -2
- 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 +128 -0
- package/src/components/Dashboard/ConfigurableDashboard.tsx +479 -0
- package/src/components/Dashboard/DashboardCard.tsx +122 -0
- package/src/components/Dashboard/EmptyCard.tsx +3 -3
- package/src/components/Dashboard/MeasurementCard.test.tsx +75 -0
- package/src/components/Dashboard/MeasurementCard.tsx +101 -0
- package/src/components/Dashboard/NutritionCard.tsx +88 -96
- package/src/components/Dashboard/RoutineCard.tsx +54 -69
- 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/Dashboard/WeightCard.tsx +36 -42
- package/src/components/Exercises/Detail/Head/ExerciseDeleteDialog.tsx +1 -1
- 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/WorkoutStats.tsx +1 -1
- package/src/components/WorkoutRoutines/widgets/forms/DayTypeSelect.tsx +1 -2
- package/src/components/WorkoutRoutines/widgets/forms/SlotForm.tsx +0 -4
- package/src/components/index.ts +0 -2
- package/src/index.tsx +0 -46
- package/src/pages/Calendar/index.tsx +2 -2
- package/src/pages/WeightOverview/index.tsx +1 -1
- package/src/routes.tsx +87 -79
- package/src/services/exerciseTranslation.ts +5 -6
- package/src/services/measurements.ts +10 -17
- package/src/services/video.test.ts +4 -4
- 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/Dashboard.tsx +0 -22
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import Button from "@mui/material/Button";
|
|
2
|
+
import Table from "@mui/material/Table";
|
|
3
|
+
import TableBody from "@mui/material/TableBody";
|
|
4
|
+
import TableCell from "@mui/material/TableCell";
|
|
5
|
+
import TableHead from "@mui/material/TableHead";
|
|
6
|
+
import TableRow from "@mui/material/TableRow";
|
|
7
|
+
import Typography from "@mui/material/Typography";
|
|
8
|
+
import { LoadingPlaceholder } from "components/Core/LoadingWidget/LoadingWidget";
|
|
9
|
+
import { DashboardCard } from "components/Dashboard/DashboardCard";
|
|
10
|
+
import { EmptyCard } from "components/Dashboard/EmptyCard";
|
|
11
|
+
import { MeasurementCategory } from "components/Measurements/models/Category";
|
|
12
|
+
import { useMeasurementsCategoryQuery } from "components/Measurements/queries";
|
|
13
|
+
import { CategoryForm } from "components/Measurements/widgets/CategoryForm";
|
|
14
|
+
import { MeasurementChart } from "components/Measurements/widgets/MeasurementChart";
|
|
15
|
+
import i18n from "i18n";
|
|
16
|
+
import React from "react";
|
|
17
|
+
import { useTranslation } from "react-i18next";
|
|
18
|
+
import Slider, { Settings } from "react-slick";
|
|
19
|
+
import { makeLink, WgerLink } from "utils/url";
|
|
20
|
+
import "slick-carousel/slick/slick.css";
|
|
21
|
+
import "slick-carousel/slick/slick-theme.css";
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
export const MeasurementCard = () => {
|
|
25
|
+
const { t } = useTranslation();
|
|
26
|
+
const categoryQuery = useMeasurementsCategoryQuery();
|
|
27
|
+
|
|
28
|
+
if (categoryQuery.isLoading) {
|
|
29
|
+
return <LoadingPlaceholder />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return categoryQuery.data === null
|
|
33
|
+
? <EmptyCard
|
|
34
|
+
title={t("measurements.measurements")}
|
|
35
|
+
modalContent={<CategoryForm />}
|
|
36
|
+
modalTitle={t("add")} />
|
|
37
|
+
: <MeasurementCardContent categories={categoryQuery.data!} />;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const MeasurementCardContent = (props: { categories: MeasurementCategory[] }) => {
|
|
41
|
+
const { t } = useTranslation();
|
|
42
|
+
|
|
43
|
+
const settings: Settings = {
|
|
44
|
+
dots: true,
|
|
45
|
+
infinite: true,
|
|
46
|
+
speed: 500,
|
|
47
|
+
slidesToShow: 1,
|
|
48
|
+
slidesToScroll: 1,
|
|
49
|
+
arrows: false,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (<>
|
|
53
|
+
<DashboardCard
|
|
54
|
+
title={t("measurements.measurements")}
|
|
55
|
+
actions={
|
|
56
|
+
<>
|
|
57
|
+
<Button
|
|
58
|
+
size="small"
|
|
59
|
+
href={makeLink(WgerLink.MEASUREMENT_OVERVIEW, i18n.language)}
|
|
60
|
+
>
|
|
61
|
+
{t("seeDetails")}
|
|
62
|
+
</Button>
|
|
63
|
+
</>
|
|
64
|
+
}
|
|
65
|
+
>
|
|
66
|
+
<div className="slider-container">
|
|
67
|
+
<Slider {...settings}>
|
|
68
|
+
{props.categories.map(c => <MeasurementCardTableContent category={c} />)}
|
|
69
|
+
</Slider>
|
|
70
|
+
</div>
|
|
71
|
+
</DashboardCard>
|
|
72
|
+
</>);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
const MeasurementCardTableContent = (props: { category: MeasurementCategory }) => {
|
|
77
|
+
const { t } = useTranslation();
|
|
78
|
+
|
|
79
|
+
return (<>
|
|
80
|
+
<Typography variant="h6" gutterBottom>
|
|
81
|
+
{props.category.name}
|
|
82
|
+
</Typography>
|
|
83
|
+
<MeasurementChart category={props.category} />
|
|
84
|
+
<Table size="small">
|
|
85
|
+
<TableHead>
|
|
86
|
+
<TableRow>
|
|
87
|
+
<TableCell>{t('date')}</TableCell>
|
|
88
|
+
<TableCell>{t('value')}</TableCell>
|
|
89
|
+
</TableRow>
|
|
90
|
+
</TableHead>
|
|
91
|
+
<TableBody>
|
|
92
|
+
{[...props.category.entries].slice(0, 5).map(entry => (
|
|
93
|
+
<TableRow key={`measurement-entry-${entry.id}`}>
|
|
94
|
+
<TableCell>{entry.date.toLocaleDateString()}</TableCell>
|
|
95
|
+
<TableCell>{entry.value} {props.category.unit}</TableCell>
|
|
96
|
+
</TableRow>
|
|
97
|
+
))}
|
|
98
|
+
</TableBody>
|
|
99
|
+
</Table>
|
|
100
|
+
</>);
|
|
101
|
+
};
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
|
2
|
-
import ExpandMoreIcon from
|
|
3
|
-
import HistoryEduIcon from
|
|
2
|
+
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
3
|
+
import HistoryEduIcon from "@mui/icons-material/HistoryEdu";
|
|
4
4
|
import PhotoIcon from "@mui/icons-material/Photo";
|
|
5
5
|
import {
|
|
6
6
|
Alert,
|
|
7
7
|
Avatar,
|
|
8
8
|
Button,
|
|
9
|
-
Card,
|
|
10
|
-
CardActions,
|
|
11
|
-
CardContent,
|
|
12
|
-
CardHeader,
|
|
13
9
|
Collapse,
|
|
14
10
|
IconButton,
|
|
15
11
|
List,
|
|
@@ -19,7 +15,7 @@ import {
|
|
|
19
15
|
ListItemIcon,
|
|
20
16
|
ListItemText,
|
|
21
17
|
Snackbar,
|
|
22
|
-
} from
|
|
18
|
+
} from "@mui/material";
|
|
23
19
|
import Tooltip from "@mui/material/Tooltip";
|
|
24
20
|
import { LoadingPlaceholder } from "components/Core/LoadingWidget/LoadingWidget";
|
|
25
21
|
import { WgerModal } from "components/Core/Modals/WgerModal";
|
|
@@ -31,16 +27,15 @@ import { useAddDiaryEntryQuery, useFetchLastNutritionalPlanQuery } from "compone
|
|
|
31
27
|
import { NutritionalValuesDashboardChart } from "components/Nutrition/widgets/charts/NutritionalValuesDashboardChart";
|
|
32
28
|
import { NutritionDiaryEntryForm } from "components/Nutrition/widgets/forms/NutritionDiaryEntryForm";
|
|
33
29
|
import { PlanForm } from "components/Nutrition/widgets/forms/PlanForm";
|
|
34
|
-
import React, { useState } from
|
|
30
|
+
import React, { useState } from "react";
|
|
35
31
|
import { useTranslation } from "react-i18next";
|
|
36
32
|
import { SNACKBAR_AUTO_HIDE_DURATION } from "utils/consts";
|
|
37
33
|
import { dateTimeToLocaleHHMM } from "utils/date";
|
|
38
34
|
import { numberGramLocale } from "utils/numbers";
|
|
39
35
|
import { makeLink, WgerLink } from "utils/url";
|
|
40
|
-
|
|
36
|
+
import { DashboardCard } from "./DashboardCard";
|
|
41
37
|
|
|
42
38
|
export const NutritionCard = () => {
|
|
43
|
-
|
|
44
39
|
const [t] = useTranslation();
|
|
45
40
|
const planQuery = useFetchLastNutritionalPlanQuery();
|
|
46
41
|
|
|
@@ -48,13 +43,11 @@ export const NutritionCard = () => {
|
|
|
48
43
|
return <LoadingPlaceholder />;
|
|
49
44
|
}
|
|
50
45
|
|
|
51
|
-
return planQuery.data !== null
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
modalTitle={t('add')}
|
|
57
|
-
/>;
|
|
46
|
+
return planQuery.data !== null ? (
|
|
47
|
+
<NutritionCardContent plan={planQuery.data!} />
|
|
48
|
+
) : (
|
|
49
|
+
<EmptyCard title={t("nutritionalPlan")} modalContent={<PlanForm />} modalTitle={t("add")} />
|
|
50
|
+
);
|
|
58
51
|
};
|
|
59
52
|
|
|
60
53
|
function NutritionCardContent(props: { plan: NutritionalPlan }) {
|
|
@@ -64,53 +57,50 @@ function NutritionCardContent(props: { plan: NutritionalPlan }) {
|
|
|
64
57
|
const handleOpenLogModal = () => setOpenLogModal(true);
|
|
65
58
|
const handleCloseLogModal = () => setOpenLogModal(false);
|
|
66
59
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
title={t('nutritionalPlan')}
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<DashboardCard
|
|
63
|
+
title={t("nutritionalPlan")}
|
|
72
64
|
subheader={props.plan.description}
|
|
73
|
-
|
|
74
|
-
|
|
65
|
+
actions={
|
|
66
|
+
<>
|
|
67
|
+
<Button
|
|
68
|
+
size="small"
|
|
69
|
+
href={makeLink(WgerLink.NUTRITION_DETAIL, i18n.language, { id: props.plan.id! })}
|
|
70
|
+
>
|
|
71
|
+
{t("seeDetails")}
|
|
72
|
+
</Button>
|
|
73
|
+
<Tooltip title={t("nutrition.logThisMealItem")}>
|
|
74
|
+
<IconButton onClick={handleOpenLogModal}>
|
|
75
|
+
<HistoryEduIcon />
|
|
76
|
+
</IconButton>
|
|
77
|
+
</Tooltip>
|
|
78
|
+
</>
|
|
79
|
+
}
|
|
80
|
+
>
|
|
75
81
|
<NutritionalValuesDashboardChart
|
|
76
82
|
percentage={props.plan.percentageValuesLoggedToday}
|
|
77
83
|
planned={props.plan.plannedNutritionalValues}
|
|
78
84
|
logged={props.plan.loggedNutritionalValuesToday}
|
|
79
85
|
/>
|
|
80
86
|
<List>
|
|
81
|
-
{props.plan.meals.map(meal =>
|
|
87
|
+
{props.plan.meals.map((meal) => (
|
|
82
88
|
<MealListItem meal={meal} planId={props.plan.id!} key={meal.id} />
|
|
83
|
-
)}
|
|
89
|
+
))}
|
|
84
90
|
</List>
|
|
85
|
-
</
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
<IconButton onClick={handleOpenLogModal}>
|
|
96
|
-
<HistoryEduIcon />
|
|
97
|
-
</IconButton>
|
|
98
|
-
</Tooltip>
|
|
99
|
-
</CardActions>
|
|
100
|
-
</Card>
|
|
101
|
-
<WgerModal title={t('nutrition.addNutritionalDiary')}
|
|
102
|
-
isOpen={openLogModal}
|
|
103
|
-
closeFn={handleCloseLogModal}>
|
|
104
|
-
<NutritionDiaryEntryForm
|
|
105
|
-
closeFn={handleCloseLogModal}
|
|
106
|
-
planId={props.plan.id!}
|
|
107
|
-
meals={props.plan.meals}
|
|
108
|
-
/>
|
|
109
|
-
</WgerModal>
|
|
110
|
-
</>;
|
|
91
|
+
</DashboardCard>
|
|
92
|
+
<WgerModal title={t("nutrition.addNutritionalDiary")} isOpen={openLogModal} closeFn={handleCloseLogModal}>
|
|
93
|
+
<NutritionDiaryEntryForm
|
|
94
|
+
closeFn={handleCloseLogModal}
|
|
95
|
+
planId={props.plan.id!}
|
|
96
|
+
meals={props.plan.meals}
|
|
97
|
+
/>
|
|
98
|
+
</WgerModal>
|
|
99
|
+
</>
|
|
100
|
+
);
|
|
111
101
|
}
|
|
112
102
|
|
|
113
|
-
const MealListItem = (props: { meal: Meal
|
|
103
|
+
const MealListItem = (props: { meal: Meal; planId: number }) => {
|
|
114
104
|
const [t, i18n] = useTranslation();
|
|
115
105
|
const addDiaryEntryQuery = useAddDiaryEntryQuery(props.planId);
|
|
116
106
|
|
|
@@ -120,7 +110,7 @@ const MealListItem = (props: { meal: Meal, planId: number }) => {
|
|
|
120
110
|
const handleToggleExpand = () => setExpandView(!expandView);
|
|
121
111
|
|
|
122
112
|
const handleCloseSnackbar = (event?: React.SyntheticEvent | Event, reason?: string) => {
|
|
123
|
-
if (reason ===
|
|
113
|
+
if (reason === "clickaway") {
|
|
124
114
|
return;
|
|
125
115
|
}
|
|
126
116
|
setOpenSnackbar(false);
|
|
@@ -134,45 +124,47 @@ const MealListItem = (props: { meal: Meal, planId: number }) => {
|
|
|
134
124
|
const primaryHeader = props.meal.name ? props.meal.name : dateTimeToLocaleHHMM(props.meal.time);
|
|
135
125
|
const secondaryHeader = props.meal.name ? dateTimeToLocaleHHMM(props.meal.time) : null;
|
|
136
126
|
|
|
137
|
-
return
|
|
138
|
-
|
|
139
|
-
<
|
|
140
|
-
{expandView ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
<
|
|
174
|
-
{
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
127
|
+
return (
|
|
128
|
+
<>
|
|
129
|
+
<ListItemButton onClick={handleToggleExpand} selected={expandView}>
|
|
130
|
+
<ListItemIcon>{expandView ? <ExpandLessIcon /> : <ExpandMoreIcon />}</ListItemIcon>
|
|
131
|
+
<ListItemText primary={primaryHeader} secondary={secondaryHeader} />
|
|
132
|
+
</ListItemButton>
|
|
133
|
+
<Collapse in={expandView} timeout="auto" unmountOnExit>
|
|
134
|
+
<List>
|
|
135
|
+
{props.meal.items.map((item) => (
|
|
136
|
+
<ListItem
|
|
137
|
+
key={item.id}
|
|
138
|
+
secondaryAction={
|
|
139
|
+
<Tooltip title={t("nutrition.logThisMealItem")}>
|
|
140
|
+
<IconButton edge="end" onClick={() => handleAddDiaryEntry(item)}>
|
|
141
|
+
<HistoryEduIcon />
|
|
142
|
+
</IconButton>
|
|
143
|
+
</Tooltip>
|
|
144
|
+
}
|
|
145
|
+
>
|
|
146
|
+
<ListItemAvatar>
|
|
147
|
+
<Avatar
|
|
148
|
+
alt={item.ingredient?.name}
|
|
149
|
+
src={item.ingredient?.image?.url}
|
|
150
|
+
sx={{ width: 45, height: 45 }}
|
|
151
|
+
>
|
|
152
|
+
<PhotoIcon />
|
|
153
|
+
</Avatar>
|
|
154
|
+
</ListItemAvatar>
|
|
155
|
+
<ListItemText
|
|
156
|
+
primary={item.ingredient?.name}
|
|
157
|
+
secondary={numberGramLocale(item.amount, i18n.language)}
|
|
158
|
+
/>
|
|
159
|
+
</ListItem>
|
|
160
|
+
))}
|
|
161
|
+
</List>
|
|
162
|
+
</Collapse>
|
|
163
|
+
<Snackbar open={openSnackbar} autoHideDuration={SNACKBAR_AUTO_HIDE_DURATION} onClose={handleCloseSnackbar}>
|
|
164
|
+
<Alert onClose={handleCloseSnackbar} severity="success" sx={{ width: "100%" }}>
|
|
165
|
+
{t("nutrition.diaryEntrySaved")}
|
|
166
|
+
</Alert>
|
|
167
|
+
</Snackbar>
|
|
168
|
+
</>
|
|
169
|
+
);
|
|
170
|
+
};
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
|
2
2
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
3
3
|
import TodayIcon from "@mui/icons-material/Today";
|
|
4
|
-
import {
|
|
5
|
-
Button,
|
|
6
|
-
Card,
|
|
7
|
-
CardActions,
|
|
8
|
-
CardContent,
|
|
9
|
-
CardHeader,
|
|
10
|
-
Collapse,
|
|
11
|
-
List,
|
|
12
|
-
ListItemButton,
|
|
13
|
-
ListItemIcon,
|
|
14
|
-
ListItemText,
|
|
15
|
-
} from '@mui/material';
|
|
4
|
+
import { Button, Collapse, List, ListItemButton, ListItemIcon, ListItemText } from "@mui/material";
|
|
16
5
|
import { LoadingPlaceholder } from "components/Core/LoadingWidget/LoadingWidget";
|
|
17
6
|
import { EmptyCard } from "components/Dashboard/EmptyCard";
|
|
18
7
|
import { getDayName } from "components/WorkoutRoutines/models/Day";
|
|
@@ -20,11 +9,11 @@ import { Routine } from "components/WorkoutRoutines/models/Routine";
|
|
|
20
9
|
import { RoutineDayData } from "components/WorkoutRoutines/models/RoutineDayData";
|
|
21
10
|
import { useActiveRoutineQuery } from "components/WorkoutRoutines/queries";
|
|
22
11
|
import { SetConfigDataDetails } from "components/WorkoutRoutines/widgets/RoutineDetailsCard";
|
|
23
|
-
import React, { useState } from
|
|
12
|
+
import React, { useState } from "react";
|
|
24
13
|
import { useTranslation } from "react-i18next";
|
|
25
14
|
import { isSameDay } from "utils/date";
|
|
26
15
|
import { makeLink, WgerLink } from "utils/url";
|
|
27
|
-
|
|
16
|
+
import { DashboardCard } from "./DashboardCard";
|
|
28
17
|
|
|
29
18
|
export const RoutineCard = () => {
|
|
30
19
|
const [t, i18n] = useTranslation();
|
|
@@ -34,37 +23,33 @@ export const RoutineCard = () => {
|
|
|
34
23
|
return <LoadingPlaceholder />;
|
|
35
24
|
}
|
|
36
25
|
|
|
37
|
-
return routineQuery.data !== null
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
/>;
|
|
26
|
+
return routineQuery.data !== null ? (
|
|
27
|
+
<RoutineCardContent routine={routineQuery.data!} />
|
|
28
|
+
) : (
|
|
29
|
+
<EmptyCard title={t("routines.routine")} link={makeLink(WgerLink.ROUTINE_ADD, i18n.language)} />
|
|
30
|
+
);
|
|
43
31
|
};
|
|
44
32
|
|
|
45
33
|
const RoutineCardContent = (props: { routine: Routine }) => {
|
|
46
34
|
const [t, i18n] = useTranslation();
|
|
47
35
|
|
|
48
|
-
return
|
|
49
|
-
<
|
|
50
|
-
title={t(
|
|
36
|
+
return (
|
|
37
|
+
<DashboardCard
|
|
38
|
+
title={t("routines.routine")}
|
|
51
39
|
subheader={props.routine.name ?? "."}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
40
|
+
actions={
|
|
41
|
+
<Button size="small" href={makeLink(WgerLink.ROUTINE_DETAIL, i18n.language, { id: props.routine.id! })}>
|
|
42
|
+
{t("seeDetails")}
|
|
43
|
+
</Button>
|
|
44
|
+
}
|
|
45
|
+
>
|
|
55
46
|
<List>
|
|
56
|
-
{props.routine.dayDataCurrentIterationNoNulls.map((dayData) =>
|
|
57
|
-
<DayListItem dayData={dayData} key={`dayDetails-${dayData.date.toISOString()}`} />
|
|
47
|
+
{props.routine.dayDataCurrentIterationNoNulls.map((dayData) => (
|
|
48
|
+
<DayListItem dayData={dayData} key={`dayDetails-${dayData.date.toISOString()}`} />
|
|
49
|
+
))}
|
|
58
50
|
</List>
|
|
59
|
-
</
|
|
60
|
-
|
|
61
|
-
<CardActions>
|
|
62
|
-
<Button size="small"
|
|
63
|
-
href={makeLink(WgerLink.ROUTINE_DETAIL, i18n.language, { id: props.routine.id! })}>
|
|
64
|
-
{t('seeDetails')}
|
|
65
|
-
</Button>
|
|
66
|
-
</CardActions>
|
|
67
|
-
</Card>;
|
|
51
|
+
</DashboardCard>
|
|
52
|
+
);
|
|
68
53
|
};
|
|
69
54
|
|
|
70
55
|
const DayListItem = (props: { dayData: RoutineDayData }) => {
|
|
@@ -72,36 +57,36 @@ const DayListItem = (props: { dayData: RoutineDayData }) => {
|
|
|
72
57
|
|
|
73
58
|
const handleToggleExpand = () => setExpandView(!expandView);
|
|
74
59
|
|
|
75
|
-
return (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
{expandView ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
{isSameDay(props.dayData.date, new Date()) ? <TodayIcon /> : undefined}
|
|
91
|
-
</ListItemIcon>
|
|
92
|
-
</ListItemButton>
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<ListItemButton
|
|
63
|
+
onClick={handleToggleExpand}
|
|
64
|
+
selected={expandView}
|
|
65
|
+
disabled={props.dayData.day === null || props.dayData.day?.isRest}
|
|
66
|
+
>
|
|
67
|
+
<ListItemIcon>{expandView ? <ExpandLessIcon /> : <ExpandMoreIcon />}</ListItemIcon>
|
|
68
|
+
<ListItemText
|
|
69
|
+
primary={getDayName(props.dayData.day)}
|
|
70
|
+
slotProps={{ secondary: { noWrap: true, style: { overflow: "hidden", textOverflow: "ellipsis" } } }}
|
|
71
|
+
secondary={props.dayData.day?.description}
|
|
72
|
+
/>
|
|
73
|
+
<ListItemIcon>{isSameDay(props.dayData.date, new Date()) ? <TodayIcon /> : undefined}</ListItemIcon>
|
|
74
|
+
</ListItemButton>
|
|
93
75
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
76
|
+
<Collapse in={expandView} timeout="auto" unmountOnExit>
|
|
77
|
+
{props.dayData.slots.map((slotData, index) => (
|
|
78
|
+
<div key={index}>
|
|
79
|
+
{slotData.setConfigs.map((setConfigData, index) => (
|
|
80
|
+
<SetConfigDataDetails
|
|
81
|
+
setConfigData={setConfigData}
|
|
82
|
+
key={index}
|
|
83
|
+
rowHeight={"70px"}
|
|
84
|
+
showExercise={true}
|
|
85
|
+
/>
|
|
86
|
+
))}
|
|
87
|
+
</div>
|
|
88
|
+
))}
|
|
89
|
+
</Collapse>
|
|
90
|
+
</>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { QueryClientProvider } from "@tanstack/react-query";
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { TrophiesCard } from "components/Dashboard/TrophiesCard";
|
|
4
|
+
import { useUserTrophiesQuery } from "components/Trophies/queries/trophies";
|
|
5
|
+
import { testQueryClient } from "tests/queryClient";
|
|
6
|
+
import { testUserTrophies } from "tests/trophies/trophiesTestData";
|
|
7
|
+
|
|
8
|
+
jest.mock("components/Trophies/queries/trophies");
|
|
9
|
+
|
|
10
|
+
describe("test the TrophiesCard component", () => {
|
|
11
|
+
|
|
12
|
+
describe("Trophies available", () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
(useUserTrophiesQuery as jest.Mock).mockImplementation(() => ({
|
|
15
|
+
isSuccess: true,
|
|
16
|
+
isLoading: false,
|
|
17
|
+
data: testUserTrophies()
|
|
18
|
+
}));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('renders the trophies correctly', async () => {
|
|
22
|
+
// Act
|
|
23
|
+
render(
|
|
24
|
+
<QueryClientProvider client={testQueryClient}>
|
|
25
|
+
<TrophiesCard />
|
|
26
|
+
</QueryClientProvider>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Assert
|
|
30
|
+
expect(useUserTrophiesQuery).toHaveBeenCalled();
|
|
31
|
+
expect(screen.getByText('Beginner')).toBeInTheDocument();
|
|
32
|
+
expect(screen.getByText('Unstoppable')).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
describe("No trophies available", () => {
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
(useUserTrophiesQuery as jest.Mock).mockImplementation(() => ({
|
|
41
|
+
isSuccess: true,
|
|
42
|
+
isLoading: false,
|
|
43
|
+
data: null
|
|
44
|
+
}));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('correctly shows custom empty card, without call to action button', async () => {
|
|
48
|
+
|
|
49
|
+
// Act
|
|
50
|
+
render(
|
|
51
|
+
<QueryClientProvider client={testQueryClient}>
|
|
52
|
+
<TrophiesCard />
|
|
53
|
+
</QueryClientProvider>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Assert
|
|
57
|
+
expect(useUserTrophiesQuery).toHaveBeenCalled();
|
|
58
|
+
expect(screen.getByText('nothingHereYet')).toBeInTheDocument();
|
|
59
|
+
expect(screen.queryByText('nothingHereYetAction')).not.toBeInTheDocument();
|
|
60
|
+
expect(screen.queryByText('add')).not.toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Button, Card, CardContent, CardHeader, CardMedia, Tooltip, Typography, } from "@mui/material";
|
|
2
|
+
import Box from "@mui/system/Box";
|
|
3
|
+
import Stack from "@mui/system/Stack";
|
|
4
|
+
import { LoadingPlaceholder } from "components/Core/LoadingWidget/LoadingWidget";
|
|
5
|
+
import { UserTrophy } from "components/Trophies/models/userTrophy";
|
|
6
|
+
import { useUserTrophiesQuery } from "components/Trophies/queries/trophies";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { useTranslation } from "react-i18next";
|
|
9
|
+
import { makeLink, WgerLink } from "utils/url";
|
|
10
|
+
import { DashboardCard } from "./DashboardCard";
|
|
11
|
+
|
|
12
|
+
export const TrophiesCard = () => {
|
|
13
|
+
const trophiesQuery = useUserTrophiesQuery();
|
|
14
|
+
|
|
15
|
+
if (trophiesQuery.isLoading) {
|
|
16
|
+
return <LoadingPlaceholder />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return trophiesQuery.data !== null
|
|
20
|
+
? <TrophiesCardContent trophies={trophiesQuery.data!} />
|
|
21
|
+
: <EmptyTrophiesCardContent />;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function TrophiesCardContent(props: { trophies: UserTrophy[] }) {
|
|
25
|
+
const { t, i18n } = useTranslation();
|
|
26
|
+
|
|
27
|
+
const tooltipWidget = (tooltip: string) => <Typography variant="body2" textAlign={'center'}>
|
|
28
|
+
{tooltip}
|
|
29
|
+
</Typography>;
|
|
30
|
+
|
|
31
|
+
return (<DashboardCard
|
|
32
|
+
title={''}
|
|
33
|
+
scrollable={false}
|
|
34
|
+
actions={
|
|
35
|
+
<>
|
|
36
|
+
<Button
|
|
37
|
+
size="small"
|
|
38
|
+
href={makeLink(WgerLink.TROPHIES, i18n.language)}
|
|
39
|
+
>
|
|
40
|
+
{t("seeDetails")}
|
|
41
|
+
</Button>
|
|
42
|
+
</>
|
|
43
|
+
}
|
|
44
|
+
>
|
|
45
|
+
<Box sx={{ overflowX: 'auto', width: '100%' }}>
|
|
46
|
+
<Stack direction="row" spacing={3} sx={{ display: 'flex' }}>
|
|
47
|
+
{props.trophies.map((userTrophy) => (
|
|
48
|
+
<Tooltip title={tooltipWidget(userTrophy.trophy.description)} arrow key={userTrophy.trophy.uuid}>
|
|
49
|
+
<Card sx={{ width: 80, flex: '0 0 auto', boxShadow: 'none' }}>
|
|
50
|
+
<CardMedia
|
|
51
|
+
component="img"
|
|
52
|
+
image={userTrophy.trophy.image}
|
|
53
|
+
title={userTrophy.trophy.name}
|
|
54
|
+
/>
|
|
55
|
+
<CardContent>
|
|
56
|
+
<Typography gutterBottom variant="body2" component="div" textAlign="center">
|
|
57
|
+
{userTrophy.trophy.name}
|
|
58
|
+
</Typography>
|
|
59
|
+
</CardContent>
|
|
60
|
+
</Card>
|
|
61
|
+
</Tooltip>
|
|
62
|
+
))}
|
|
63
|
+
</Stack>
|
|
64
|
+
</Box>
|
|
65
|
+
</DashboardCard>);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const EmptyTrophiesCardContent = () => {
|
|
69
|
+
const [t] = useTranslation();
|
|
70
|
+
|
|
71
|
+
return (<>
|
|
72
|
+
<Card sx={{ paddingTop: 0, height: "100%", }}>
|
|
73
|
+
<CardHeader
|
|
74
|
+
title={t("trophies.trophies")}
|
|
75
|
+
sx={{ paddingBottom: 0 }}
|
|
76
|
+
/>
|
|
77
|
+
<CardContent>
|
|
78
|
+
<Typography variant="h6" mr={3}>
|
|
79
|
+
{t('nothingHereYet')}
|
|
80
|
+
</Typography>
|
|
81
|
+
</CardContent>
|
|
82
|
+
</Card>
|
|
83
|
+
</>);
|
|
84
|
+
};
|