@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.
Files changed (69) hide show
  1. package/build/assets/ajax-loader.gif +0 -0
  2. package/build/assets/index.css +1 -1
  3. package/build/assets/slick.svg +14 -0
  4. package/build/locales/de/translation.json +25 -6
  5. package/build/locales/en/translation.json +12 -0
  6. package/build/locales/es/translation.json +22 -3
  7. package/build/locales/fr/translation.json +10 -1
  8. package/build/locales/hi/translation.json +8 -0
  9. package/build/locales/nl/translation.json +350 -239
  10. package/build/locales/pt_BR/translation.json +349 -255
  11. package/build/locales/ru/translation.json +39 -3
  12. package/build/locales/uk/translation.json +10 -1
  13. package/build/main.js +170 -166
  14. package/build/main.js.map +1 -1
  15. package/package.json +8 -2
  16. package/src/components/BodyWeight/TableDashboard/TableDashboard.tsx +4 -6
  17. package/src/components/Calendar/Components/CalendarComponent.test.tsx +18 -22
  18. package/src/components/Calendar/Components/CalendarComponent.tsx +11 -8
  19. package/src/components/Calendar/Components/CalendarHeader.tsx +3 -3
  20. package/src/components/Calendar/Components/Entries.tsx +8 -3
  21. package/src/components/Dashboard/CalendarCard.tsx +16 -0
  22. package/src/components/Dashboard/ConfigurableDashboard.test.ts +128 -0
  23. package/src/components/Dashboard/ConfigurableDashboard.tsx +479 -0
  24. package/src/components/Dashboard/DashboardCard.tsx +122 -0
  25. package/src/components/Dashboard/EmptyCard.tsx +3 -3
  26. package/src/components/Dashboard/MeasurementCard.test.tsx +75 -0
  27. package/src/components/Dashboard/MeasurementCard.tsx +101 -0
  28. package/src/components/Dashboard/NutritionCard.tsx +88 -96
  29. package/src/components/Dashboard/RoutineCard.tsx +54 -69
  30. package/src/components/Dashboard/TrophiesCard.test.tsx +63 -0
  31. package/src/components/Dashboard/TrophiesCard.tsx +84 -0
  32. package/src/components/Dashboard/WeightCard.test.tsx +0 -10
  33. package/src/components/Dashboard/WeightCard.tsx +36 -42
  34. package/src/components/Exercises/Detail/Head/ExerciseDeleteDialog.tsx +1 -1
  35. package/src/components/Measurements/Screens/MeasurementCategoryOverview.tsx +1 -1
  36. package/src/components/Measurements/models/Category.ts +13 -2
  37. package/src/components/Measurements/models/Entry.ts +13 -2
  38. package/src/components/Trophies/components/TrophiesDetail.test.tsx +34 -0
  39. package/src/components/Trophies/components/TrophiesDetail.tsx +88 -0
  40. package/src/components/Trophies/models/trophy.test.ts +33 -0
  41. package/src/components/Trophies/models/trophy.ts +75 -0
  42. package/src/components/Trophies/models/userTrophy.test.ts +38 -0
  43. package/src/components/Trophies/models/userTrophy.ts +67 -0
  44. package/src/components/Trophies/models/userTrophyProgression.test.ts +43 -0
  45. package/src/components/Trophies/models/userTrophyProgression.ts +68 -0
  46. package/src/components/Trophies/queries/trophies.ts +31 -0
  47. package/src/components/Trophies/services/trophies.ts +22 -0
  48. package/src/components/Trophies/services/userTrophies.ts +33 -0
  49. package/src/components/Trophies/services/userTrophyProgression.ts +16 -0
  50. package/src/components/WorkoutRoutines/Detail/WorkoutStats.tsx +1 -1
  51. package/src/components/WorkoutRoutines/widgets/forms/DayTypeSelect.tsx +1 -2
  52. package/src/components/WorkoutRoutines/widgets/forms/SlotForm.tsx +0 -4
  53. package/src/components/index.ts +0 -2
  54. package/src/index.tsx +0 -46
  55. package/src/pages/Calendar/index.tsx +2 -2
  56. package/src/pages/WeightOverview/index.tsx +1 -1
  57. package/src/routes.tsx +87 -79
  58. package/src/services/exerciseTranslation.ts +5 -6
  59. package/src/services/measurements.ts +10 -17
  60. package/src/services/video.test.ts +4 -4
  61. package/src/tests/trophies/trophiesTestData.ts +80 -0
  62. package/src/utils/consts.ts +18 -3
  63. package/src/utils/url.test.ts +32 -1
  64. package/src/utils/url.ts +24 -3
  65. package/src/components/Carousel/carousel.module.css +0 -43
  66. package/src/components/Carousel/carousel.module.css.map +0 -1
  67. package/src/components/Carousel/carousel.module.scss +0 -46
  68. package/src/components/Carousel/index.tsx +0 -66
  69. 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 '@mui/icons-material/ExpandMore';
3
- import HistoryEduIcon from '@mui/icons-material/HistoryEdu';
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 '@mui/material';
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 'react';
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
- ? <NutritionCardContent plan={planQuery.data!} />
53
- : <EmptyCard
54
- title={t('nutritionalPlan')}
55
- modalContent={<PlanForm />}
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
- return <>
69
- <Card>
70
- <CardHeader
71
- title={t('nutritionalPlan')}
60
+ return (
61
+ <>
62
+ <DashboardCard
63
+ title={t("nutritionalPlan")}
72
64
  subheader={props.plan.description}
73
- />
74
- <CardContent sx={{ height: "500px", overflow: "auto" }}>
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
- </CardContent>
86
- <CardActions sx={{
87
- justifyContent: "space-between",
88
- alignItems: "flex-start",
89
- }}>
90
- <Button size="small"
91
- href={makeLink(WgerLink.NUTRITION_DETAIL, i18n.language, { id: props.plan.id! })}>
92
- {t('seeDetails')}
93
- </Button>
94
- <Tooltip title={t('nutrition.logThisMealItem')}>
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, planId: number }) => {
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 === 'clickaway') {
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
- <ListItemButton onClick={handleToggleExpand} selected={expandView}>
139
- <ListItemIcon>
140
- {expandView ? <ExpandLessIcon /> : <ExpandMoreIcon />}
141
- </ListItemIcon>
142
- <ListItemText primary={primaryHeader} secondary={secondaryHeader} />
143
-
144
- </ListItemButton>
145
- <Collapse in={expandView} timeout="auto" unmountOnExit>
146
- <List>
147
- {props.meal.items.map((item) =>
148
- <ListItem key={item.id} secondaryAction={
149
- <Tooltip title={t('nutrition.logThisMealItem')}>
150
- <IconButton edge="end" onClick={() => handleAddDiaryEntry(item)}>
151
- <HistoryEduIcon />
152
- </IconButton>
153
- </Tooltip>
154
- }>
155
- <ListItemAvatar>
156
- <Avatar
157
- alt={item.ingredient?.name}
158
- src={item.ingredient?.image?.url}
159
- sx={{ width: 45, height: 45 }}
160
- >
161
- <PhotoIcon />
162
- </Avatar>
163
- </ListItemAvatar>
164
- <ListItemText
165
- primary={item.ingredient?.name}
166
- secondary={numberGramLocale(item.amount, i18n.language)}
167
- />
168
- </ListItem>
169
- )}
170
- </List>
171
- </Collapse>
172
- <Snackbar open={openSnackbar} autoHideDuration={SNACKBAR_AUTO_HIDE_DURATION} onClose={handleCloseSnackbar}>
173
- <Alert onClose={handleCloseSnackbar} severity="success" sx={{ width: '100%' }}>
174
- {t('nutrition.diaryEntrySaved')}
175
- </Alert>
176
- </Snackbar>
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 'react';
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
- ? <RoutineCardContent routine={routineQuery.data!} />
39
- : <EmptyCard
40
- title={t('routines.routine')}
41
- link={makeLink(WgerLink.ROUTINE_ADD, i18n.language)}
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 <Card>
49
- <CardHeader
50
- title={t('routines.routine')}
36
+ return (
37
+ <DashboardCard
38
+ title={t("routines.routine")}
51
39
  subheader={props.routine.name ?? "."}
52
- />
53
- {/* Note: not 500 like the other cards, but a bit more since we don't have an action icon... */}
54
- <CardContent sx={{ height: "510px", overflow: "auto" }}>
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
- </CardContent>
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
- <ListItemButton
77
- onClick={handleToggleExpand}
78
- selected={expandView}
79
- disabled={props.dayData.day === null || props.dayData.day?.isRest}
80
- >
81
- <ListItemIcon>
82
- {expandView ? <ExpandLessIcon /> : <ExpandMoreIcon />}
83
- </ListItemIcon>
84
- <ListItemText
85
- primary={getDayName(props.dayData.day)}
86
- slotProps={{ secondary: { noWrap: true, style: { overflow: 'hidden', textOverflow: 'ellipsis' } } }}
87
- secondary={props.dayData.day?.description}
88
- />
89
- <ListItemIcon>
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
- <Collapse in={expandView} timeout="auto" unmountOnExit>
95
- {props.dayData.slots.map((slotData, index) => (<div key={index}>
96
- {slotData.setConfigs.map((setConfigData, index) =>
97
- <SetConfigDataDetails
98
- setConfigData={setConfigData}
99
- key={index}
100
- rowHeight={'70px'}
101
- showExercise={true}
102
- />
103
- )}
104
- </div>))}
105
- </Collapse>
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
+ };