@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.
Files changed (72) 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/ar/translation.json +347 -236
  5. package/build/locales/cs/translation.json +257 -253
  6. package/build/locales/de/translation.json +13 -3
  7. package/build/locales/en/translation.json +4 -1
  8. package/build/locales/es/translation.json +26 -4
  9. package/build/locales/fr/translation.json +10 -1
  10. package/build/locales/hi/translation.json +4 -1
  11. package/build/locales/hr/translation.json +15 -3
  12. package/build/locales/mk/translation.json +138 -0
  13. package/build/locales/nl/translation.json +118 -11
  14. package/build/locales/pt/translation.json +338 -338
  15. package/build/locales/pt_BR/translation.json +15 -3
  16. package/build/locales/ru/translation.json +43 -3
  17. package/build/locales/ta/translation.json +1 -1
  18. package/build/locales/uk/translation.json +13 -1
  19. package/build/locales/zh_Hans/translation.json +25 -1
  20. package/build/locales/zh_Hant/translation.json +255 -243
  21. package/build/main.js +170 -170
  22. package/build/main.js.map +1 -1
  23. package/package.json +15 -13
  24. package/src/components/BodyWeight/TableDashboard/TableDashboard.tsx +4 -6
  25. package/src/components/Calendar/Components/CalendarComponent.test.tsx +18 -22
  26. package/src/components/Calendar/Components/CalendarComponent.tsx +11 -8
  27. package/src/components/Calendar/Components/CalendarHeader.tsx +3 -3
  28. package/src/components/Calendar/Components/Entries.tsx +8 -3
  29. package/src/components/Dashboard/CalendarCard.tsx +16 -0
  30. package/src/components/Dashboard/ConfigurableDashboard.test.ts +129 -0
  31. package/src/components/Dashboard/ConfigurableDashboard.tsx +352 -89
  32. package/src/components/Dashboard/DashboardCard.tsx +3 -2
  33. package/src/components/Dashboard/MeasurementCard.test.tsx +75 -0
  34. package/src/components/Dashboard/MeasurementCard.tsx +101 -0
  35. package/src/components/Dashboard/RoutineCard.tsx +1 -1
  36. package/src/components/Dashboard/TrophiesCard.test.tsx +63 -0
  37. package/src/components/Dashboard/TrophiesCard.tsx +84 -0
  38. package/src/components/Dashboard/WeightCard.test.tsx +0 -10
  39. package/src/components/Measurements/Screens/MeasurementCategoryOverview.tsx +1 -1
  40. package/src/components/Measurements/models/Category.ts +13 -2
  41. package/src/components/Measurements/models/Entry.ts +13 -2
  42. package/src/components/Trophies/components/TrophiesDetail.test.tsx +34 -0
  43. package/src/components/Trophies/components/TrophiesDetail.tsx +88 -0
  44. package/src/components/Trophies/models/trophy.test.ts +33 -0
  45. package/src/components/Trophies/models/trophy.ts +75 -0
  46. package/src/components/Trophies/models/userTrophy.test.ts +38 -0
  47. package/src/components/Trophies/models/userTrophy.ts +67 -0
  48. package/src/components/Trophies/models/userTrophyProgression.test.ts +43 -0
  49. package/src/components/Trophies/models/userTrophyProgression.ts +68 -0
  50. package/src/components/Trophies/queries/trophies.ts +31 -0
  51. package/src/components/Trophies/services/trophies.ts +22 -0
  52. package/src/components/Trophies/services/userTrophies.ts +33 -0
  53. package/src/components/Trophies/services/userTrophyProgression.ts +16 -0
  54. package/src/components/WorkoutRoutines/Detail/RoutineDetail.tsx +1 -1
  55. package/src/components/WorkoutRoutines/Detail/TemplateDetail.tsx +1 -1
  56. package/src/components/WorkoutRoutines/models/Routine.test.ts +17 -0
  57. package/src/components/WorkoutRoutines/models/Routine.ts +20 -3
  58. package/src/components/WorkoutRoutines/widgets/RoutineDetailsCard.tsx +1 -1
  59. package/src/components/index.ts +0 -2
  60. package/src/pages/Calendar/index.tsx +2 -2
  61. package/src/pages/WeightOverview/index.tsx +1 -1
  62. package/src/routes.tsx +6 -1
  63. package/src/services/measurements.ts +10 -17
  64. package/src/tests/trophies/trophiesTestData.ts +80 -0
  65. package/src/utils/consts.ts +18 -3
  66. package/src/utils/url.test.ts +32 -1
  67. package/src/utils/url.ts +24 -3
  68. package/src/components/Carousel/carousel.module.css +0 -43
  69. package/src/components/Carousel/carousel.module.css.map +0 -1
  70. package/src/components/Carousel/carousel.module.scss +0 -46
  71. package/src/components/Carousel/index.tsx +0 -66
  72. package/src/components/Dashboard/GoalCard.tsx +0 -71
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "exports": {
12
12
  ".": "./build/index.js"
13
13
  },
14
- "version": "25.12.5",
14
+ "version": "26.2.26",
15
15
  "repository": "https://github.com/wger-project/react",
16
16
  "type": "module",
17
17
  "publishConfig": {
@@ -22,12 +22,12 @@
22
22
  "@emotion/react": "^11.14.0",
23
23
  "@emotion/styled": "^11.14.1",
24
24
  "@hello-pangea/dnd": "^18.0.1",
25
- "@mui/icons-material": "^7.3.5",
25
+ "@mui/icons-material": "^7.3.7",
26
26
  "@mui/lab": "^7.0.1-beta.18",
27
27
  "@mui/material": "^7.3.4",
28
28
  "@mui/system": "^7.3.3",
29
- "@mui/x-data-grid": "^8.18.0",
30
- "@mui/x-date-pickers": "^8.18.0",
29
+ "@mui/x-data-grid": "^8.26.0",
30
+ "@mui/x-date-pickers": "^8.26.0",
31
31
  "@tanstack/react-query": "^5.90.2",
32
32
  "@vitejs/plugin-react": "^5.1.1",
33
33
  "axios": "^1.12.2",
@@ -38,14 +38,16 @@
38
38
  "i18next-http-backend": "^3.0.2",
39
39
  "luxon": "^3.7.2",
40
40
  "react": "^19.2.0",
41
- "react-dom": "^19.2.0",
42
- "react-grid-layout": "^1.5.2",
41
+ "react-dom": "^19.2.4",
42
+ "react-grid-layout": "^2.2.2",
43
43
  "react-i18next": "^16.3.3",
44
- "react-is": "^19.2.0",
44
+ "react-is": "^19.2.4",
45
45
  "react-responsive": "^10.0.1",
46
- "react-router-dom": "^7.9.4",
46
+ "react-router-dom": "^7.12.0",
47
47
  "react-simple-wysiwyg": "^3.4.1",
48
+ "react-slick": "^0.31.0",
48
49
  "recharts": "^3.4.1",
50
+ "slick-carousel": "^1.8.1",
49
51
  "slug": "^9.1.0",
50
52
  "typescript": "^5.9.3",
51
53
  "vite-tsconfig-paths": "^5.1.4",
@@ -65,25 +67,25 @@
65
67
  "@types/node": "^22.18.9",
66
68
  "@types/react": "^19.2.5",
67
69
  "@types/react-dom": "^19.2.3",
68
- "@types/react-grid-layout": "^1.3.5",
69
70
  "@types/react-is": "^19.2.0",
71
+ "@types/react-slick": "^0.23.13",
70
72
  "@types/slug": "^5.0.9",
71
73
  "@typescript-eslint/eslint-plugin": "^8.47.0",
72
74
  "@typescript-eslint/parser": "^8.47.0",
73
- "eslint": "^9.37.0",
75
+ "eslint": "^9.39.2",
74
76
  "eslint-plugin-import": "^2.32.0",
75
77
  "eslint-plugin-jsx-a11y": "^6.10.2",
76
78
  "eslint-plugin-react": "^7.37.5",
77
79
  "eslint-plugin-react-hooks": "^7.0.0",
78
- "i18next-parser": "^9.3.0",
80
+ "i18next-cli": "^1.46.0",
79
81
  "jest": "^30.2.0",
80
82
  "jest-environment-jsdom": "^30.2.0",
81
83
  "jsdom": "^27.2.0",
82
84
  "ts-jest": "^29.4.5",
83
85
  "typescript-eslint": "^8.46.0",
84
- "vite": "^7.2.2",
86
+ "vite": "^7.3.1",
85
87
  "vite-plugin-eslint": "^1.8.1",
86
- "vitest": "^3.2.4",
88
+ "vitest": "^4.0.18",
87
89
  "webpack-bundle-analyzer": "^4.10.2"
88
90
  },
89
91
  "scripts": {
@@ -41,17 +41,15 @@ export const WeightTableDashboard = ({ weights }: WeightTableProps) => {
41
41
  <Table size={"small"}>
42
42
  <TableHead>
43
43
  <TableRow>
44
- <TableCell align="center">{t('date')}</TableCell>
45
- <TableCell align="center">{t('weight')}</TableCell>
44
+ <TableCell>{t('date')}</TableCell>
45
+ <TableCell>{t('weight')}</TableCell>
46
46
  </TableRow>
47
47
  </TableHead>
48
48
  <TableBody>
49
49
  {filteredWeight.map((row) => (
50
50
  <TableRow key={row.date.toISOString()}>
51
- <TableCell align="center">
52
- {dateTimeToLocale(row.date)}
53
- </TableCell>
54
- <TableCell align="center">{row.weight}</TableCell>
51
+ <TableCell>{dateTimeToLocale(row.date)}</TableCell>
52
+ <TableCell>{row.weight}</TableCell>
55
53
  </TableRow>
56
54
  ))}
57
55
  </TableBody>
@@ -33,33 +33,29 @@ describe('CalendarComponent', () => {
33
33
  beforeEach(() => {
34
34
 
35
35
  (getWeights as jest.Mock).mockImplementation(() => Promise.resolve([
36
- new WeightEntry(
37
- new Date(currentYear, currentMonth, 2, 12, 0),
38
- 70
39
- ),
40
- ]
41
- ));
42
-
43
- (getSessions as jest.Mock).mockImplementation(() => Promise.resolve([
44
- testWorkoutSession
45
- ]
36
+ new WeightEntry(
37
+ new Date(currentYear, currentMonth, 2, 12, 0),
38
+ 70
39
+ ),
40
+ ]));
41
+
42
+ (getSessions as jest.Mock).mockImplementation(() => Promise.resolve(
43
+ [testWorkoutSession]
46
44
  ));
47
45
 
48
46
  (getMeasurementCategories as jest.Mock).mockImplementation(() => Promise.resolve([
49
- new MeasurementCategory(
50
- 1,
51
- "Body Fat",
52
- "%",
53
- [new MeasurementEntry(1, 1, new Date(currentYear, currentMonth, 1, 12, 0), 20, "Normal")]
54
- ),
55
- ],
56
- ));
47
+ new MeasurementCategory(
48
+ 1,
49
+ "Body Fat",
50
+ "%",
51
+ [new MeasurementEntry(1, 1, new Date(currentYear, currentMonth, 1, 12, 0), 20, "Normal")]
52
+ ),
53
+ ]));
57
54
 
58
55
  (getNutritionalDiaryEntries as jest.Mock).mockImplementation(() => Promise.resolve([
59
- TEST_DIARY_ENTRY_1,
60
- TEST_DIARY_ENTRY_2,
61
- ],
62
- ));
56
+ TEST_DIARY_ENTRY_1,
57
+ TEST_DIARY_ENTRY_2,
58
+ ]));
63
59
 
64
60
  testQueryClient.clear();
65
61
  });
@@ -25,7 +25,7 @@ export interface DayProps {
25
25
  }
26
26
 
27
27
 
28
- const CalendarComponent = () => {
28
+ const CalendarComponent = (props: { showBorder?: boolean }) => {
29
29
  const [t] = useTranslation();
30
30
 
31
31
  const currentDate = new Date();
@@ -35,6 +35,8 @@ const CalendarComponent = () => {
35
35
  const startOfMonth = new Date(currentYear, currentMonth, 1);
36
36
  const endOfMonth = new Date(currentYear, currentMonth + 1, 0);
37
37
 
38
+ const showBorder = props.showBorder ?? true;
39
+
38
40
 
39
41
  const weightsQuery = useBodyWeightQuery();
40
42
  const sessionQuery = useSessionsQuery({
@@ -175,14 +177,15 @@ const CalendarComponent = () => {
175
177
  display: 'flex',
176
178
  gap: 2,
177
179
  flexDirection: { xs: 'column', md: 'row' },
178
- height: { xs: 'auto', md: 'calc(100vh - 130px)' },
180
+ // height: { xs: 'auto', md: 'calc(100vh - 130px)' },
179
181
  width: '100%',
180
182
  }}>
181
183
  <Card sx={{
184
+ boxShadow: showBorder ? undefined : 'none',
182
185
  width: { xs: 'auto', md: '65%' },
183
186
  height: { xs: 'auto', md: '100%' },
184
- m: { xs: 0, sm: 1, md: 2 },
185
- p: { xs: 1, sm: 1.5, md: 2 },
187
+ // m: { xs: 0, sm: 1, md: 2 },
188
+ // p: { xs: 1, sm: 1.5, md: 2 },
186
189
  display: 'flex',
187
190
  flexDirection: 'column'
188
191
  }}>
@@ -193,7 +196,7 @@ const CalendarComponent = () => {
193
196
  <div style={{
194
197
  display: 'flex',
195
198
  alignItems: 'center',
196
- marginBottom: '16px'
199
+ // marginBottom: '16px'
197
200
  }}>
198
201
  <CalendarMonthIcon style={{
199
202
  width: isMobile ? '28px' : '32px',
@@ -235,14 +238,14 @@ const CalendarComponent = () => {
235
238
  sx={{
236
239
  width: { xs: 'auto', md: '65%' },
237
240
  height: { xs: '60%', md: '100%' },
238
- m: { xs: 0, sm: 1, md: 2 },
239
- p: { xs: 1, sm: 1.5, md: 2 }
241
+ // m: { xs: 0, sm: 1, md: 2 },
242
+ // p: { xs: 1, sm: 1.5, md: 2 }
240
243
  }}
241
244
  >
242
245
  <LoadingPlaceholder />
243
246
  </Card>}
244
247
 
245
- {isSuccess && <Entries selectedDay={selectedDay} />}
248
+ {isSuccess && <Entries selectedDay={selectedDay} showBorder={showBorder} />}
246
249
  </Box>
247
250
  );
248
251
  };
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Button, Typography } from '@mui/material';
3
- import {useTranslation} from "react-i18next";
3
+ import { useTranslation } from "react-i18next";
4
4
 
5
5
  interface CalendarHeaderProps {
6
6
  currentMonth: number;
@@ -9,7 +9,7 @@ interface CalendarHeaderProps {
9
9
  onNextMonth: () => void;
10
10
  }
11
11
 
12
- const CalendarHeader: React.FC<CalendarHeaderProps> = ({currentMonth, currentYear, onPrevMonth, onNextMonth,}) => {
12
+ const CalendarHeader: React.FC<CalendarHeaderProps> = ({ currentMonth, currentYear, onPrevMonth, onNextMonth, }) => {
13
13
  const { i18n } = useTranslation();
14
14
  const months = Array.from({ length: 12 }, (_, index) =>
15
15
  new Date(2024, index, 1).toLocaleString(i18n.language, { month: "long" })
@@ -21,7 +21,7 @@ const CalendarHeader: React.FC<CalendarHeaderProps> = ({currentMonth, currentYea
21
21
  alignItems: 'center',
22
22
  justifyContent: 'space-between',
23
23
  width: '100%',
24
- paddingBottom: '3%'
24
+ paddingBottom: '1em',
25
25
  }}>
26
26
  <Button variant="outlined" onClick={onPrevMonth}>
27
27
  &lt;
@@ -17,22 +17,27 @@ import { DayProps } from "./CalendarComponent";
17
17
 
18
18
  interface LogProps {
19
19
  selectedDay: DayProps;
20
+ showBorder?: boolean;
20
21
  }
21
22
 
22
- const Entries: React.FC<LogProps> = ({ selectedDay }) => {
23
+ const Entries: React.FC<LogProps> = ({ selectedDay, showBorder }) => {
23
24
  const [t] = useTranslation();
24
25
 
25
26
  const [openMeasurements, setOpenMeasurements] = React.useState(false);
26
27
  const [openSession, setOpenSession] = React.useState(false);
27
28
  const [openNutritionDiary, setOpenNutritionDiary] = React.useState(false);
28
29
 
30
+ showBorder = showBorder ?? true;
31
+
29
32
  return (
30
33
  <Card
31
34
  sx={{
35
+ boxShadow: showBorder ? undefined : 'none',
32
36
  width: { xs: 'auto', md: '45%' },
33
37
  height: { xs: '60%', md: '100%' },
34
- m: { xs: 0, sm: 1, md: 2 },
35
- p: { xs: 1, sm: 1.5, md: 2 }
38
+ // m: { xs: 0, sm: 1, md: 2 },
39
+ // p: { xs: 1, sm: 1.5, md: 2 }
40
+
36
41
  }}
37
42
  >
38
43
  <CardHeader
@@ -0,0 +1,16 @@
1
+ import CalendarComponent from "components/Calendar/Components/CalendarComponent";
2
+ import { DashboardCard } from "components/Dashboard/DashboardCard";
3
+ import React from "react";
4
+ import { useTranslation } from "react-i18next";
5
+
6
+
7
+ export const CalendarCard = () => {
8
+ const { t } = useTranslation();
9
+
10
+
11
+ return (
12
+ <DashboardCard title={t("calendar")}>
13
+ <CalendarComponent showBorder={false} />
14
+ </DashboardCard>
15
+ );
16
+ };
@@ -0,0 +1,129 @@
1
+ import { AVAILABLE_WIDGETS, loadDashboardState, } from './ConfigurableDashboard';
2
+
3
+ describe('loadDashboardState migration', () => {
4
+ beforeEach(() => {
5
+ localStorage.clear();
6
+ });
7
+
8
+ test('filters unknown ids and falls back to all widgets when none valid', () => {
9
+ // Arrange
10
+ const data = {
11
+ "version": 1,
12
+ "selectedWidgetIds": ["foo"],
13
+ "layouts": {
14
+ "lg": [
15
+ { "i": "foo", "w": 4, "h": 5, "x": 0, "y": 0, "minW": 3, "minH": 2 },
16
+ ],
17
+ "md": [],
18
+ "sm": [],
19
+ "xs": []
20
+ },
21
+ };
22
+ localStorage.setItem('dashboard-state', JSON.stringify(data));
23
+
24
+ // Act
25
+ const res = loadDashboardState();
26
+
27
+ // Assert
28
+ expect(res).not.toBeNull();
29
+ const allowed = AVAILABLE_WIDGETS.map((w) => w.id).slice().sort();
30
+ expect(res!.selectedWidgetIds.slice().sort()).toEqual(allowed);
31
+ });
32
+
33
+ test('removes unknown IDs', () => {
34
+ // Arrange
35
+ const data = {
36
+ "version": 1,
37
+ "selectedWidgetIds": ["routine", "foo"],
38
+ "layouts": {
39
+ "lg": [
40
+ { "i": "routine", "w": 4, "h": 5, "x": 0, "y": 0, "minW": 3, "minH": 2 },
41
+ { "i": "foo", "w": 4, "h": 5, "x": 0, "y": 0, "minW": 3, "minH": 2 },
42
+ ],
43
+ "md": [],
44
+ "sm": [],
45
+ "xs": []
46
+ }
47
+ };
48
+ localStorage.setItem('dashboard-state', JSON.stringify(data));
49
+
50
+ // Act
51
+ const res = loadDashboardState();
52
+
53
+ // Assert
54
+ expect(res).not.toBeNull();
55
+ expect(res!.selectedWidgetIds).not.toContain('foo');
56
+ expect(res!.selectedWidgetIds).toContain('routine');
57
+ const lg = res?.layouts?.lg ?? [];
58
+ expect(lg.length).toEqual(1);
59
+ expect(lg[0]?.i).toEqual('routine');
60
+ });
61
+
62
+ test('migrates old top-level structure', () => {
63
+ // Arrange
64
+ const oldData = {
65
+ lg: [{ i: 'routine', x: 0, y: 0, w: 4, h: 5 }, { i: 'weight', x: 4, y: 0, w: 4, h: 5 }],
66
+ md: [],
67
+ sm: []
68
+ };
69
+ localStorage.setItem('dashboard-state', JSON.stringify(oldData));
70
+
71
+ // Act
72
+ const res = loadDashboardState();
73
+
74
+ // Assert
75
+ expect(res).not.toBeNull();
76
+ expect(res!.version).toBe(1);
77
+ // selectedWidgetIds should contain the extracted ids
78
+ expect(res!.selectedWidgetIds).toEqual(expect.arrayContaining(['routine', 'weight']));
79
+
80
+ const savedRaw = localStorage.getItem('dashboard-state');
81
+ expect(savedRaw).not.toBeNull();
82
+ const saved = JSON.parse(savedRaw!);
83
+ expect(saved.version).toBe(1);
84
+ expect(Array.isArray(saved.selectedWidgetIds)).toBe(true);
85
+ expect(saved.selectedWidgetIds).toEqual(expect.arrayContaining(['routine', 'weight']));
86
+ expect(saved.layouts).toBeDefined();
87
+ expect(Array.isArray(saved.layouts.lg)).toBe(true);
88
+ expect(saved.layouts.lg.length).toBe(2);
89
+ });
90
+
91
+ test('adds newly available widgets to selected when they are neither selected nor hidden', () => {
92
+ // Arrange: saved state has 'routine' selected and 'nutrition' hidden, other widgets are missing
93
+ const data = {
94
+ version: 1,
95
+ selectedWidgetIds: ['routine'],
96
+ hiddenWidgetIds: ['nutrition'],
97
+ layouts: {
98
+ lg: [
99
+ { i: 'routine', w: 4, h: 5, x: 0, y: 0, minW: 3, minH: 2 }
100
+ ],
101
+ md: [],
102
+ sm: [],
103
+ xs: []
104
+ }
105
+ };
106
+ localStorage.setItem('dashboard-state', JSON.stringify(data));
107
+
108
+ // Act
109
+ const res = loadDashboardState();
110
+
111
+ // Assert
112
+ expect(res).not.toBeNull();
113
+ expect(res!.hiddenWidgetIds).toEqual(['nutrition']);
114
+ expect(res!.selectedWidgetIds).toEqual(expect.arrayContaining(['routine']));
115
+ const expectedSelected = AVAILABLE_WIDGETS.map((w) => w.id).filter((id) => id !== 'nutrition');
116
+ expectedSelected.forEach((id) => {
117
+ expect(res!.selectedWidgetIds).toEqual(expect.arrayContaining([id]));
118
+ });
119
+
120
+ // And the persisted state should also include the newly added widgets
121
+ const savedRaw = localStorage.getItem('dashboard-state');
122
+ expect(savedRaw).not.toBeNull();
123
+ const saved = JSON.parse(savedRaw!);
124
+ expectedSelected.forEach((id) => {
125
+ expect(saved.selectedWidgetIds).toEqual(expect.arrayContaining([id]));
126
+ });
127
+ expect(saved.hiddenWidgetIds).toEqual(expect.arrayContaining(['nutrition']));
128
+ });
129
+ });