• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wger-project / react / 13858499267

14 Mar 2025 02:15PM UTC coverage: 76.635% (+0.7%) from 75.927%
13858499267

Pull #975

github

web-flow
Merge 36957ae7b into c59fcf558
Pull Request #975: UI for flexible routines

1273 of 1857 branches covered (68.55%)

Branch coverage included in aggregate %.

2061 of 2538 new or added lines in 104 files covered. (81.21%)

3 existing lines in 3 files now uncovered.

5159 of 6536 relevant lines covered (78.93%)

25.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.49
/src/components/Exercises/Add/Step6Overview.tsx
1
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
2✔
2
import {
2✔
3
    Alert,
4
    AlertTitle,
5
    Box,
6
    Button,
7
    ImageListItem,
8
    Table,
9
    TableBody,
10
    TableCell,
11
    TableContainer,
12
    TableRow,
13
    Typography
14
} from "@mui/material";
15
import Grid from '@mui/material/Grid2';
2✔
16
import ImageList from "@mui/material/ImageList";
2✔
17
import { LoadingPlaceholder } from "components/Core/LoadingWidget/LoadingWidget";
2✔
18
import { StepProps } from "components/Exercises/Add/AddExerciseStepper";
19
import { Note } from "components/Exercises/models/note";
2✔
20
import { useCategoriesQuery, useEquipmentQuery, useLanguageQuery, useMusclesQuery } from "components/Exercises/queries";
2✔
21
import { useProfileQuery } from "components/User/queries/profile";
2✔
22
import React, { useState } from "react";
2✔
23
import { useTranslation } from "react-i18next";
2✔
24
import { useNavigate } from "react-router-dom";
2✔
25
import { addExercise, addTranslation, postAlias, postExerciseImage } from "services";
2✔
26
import { addNote } from "services/note";
2✔
27
import { addVariation } from "services/variation";
2✔
28
import { useExerciseSubmissionStateValue } from "state";
2✔
29
import { ENGLISH_LANGUAGE_ID } from "utils/consts";
2✔
30
import { getTranslationKey } from "utils/strings";
2✔
31
import { makeLink, WgerLink } from "utils/url";
2✔
32

33
export const Step6Overview = ({ onBack }: StepProps) => {
2✔
34
    const [t, i18n] = useTranslation();
2✔
35
    const [state] = useExerciseSubmissionStateValue();
2✔
36

37
    const navigate = useNavigate();
2✔
38
    const categoryQuery = useCategoriesQuery();
2✔
39
    const languageQuery = useLanguageQuery();
2✔
40
    const musclesQuery = useMusclesQuery();
2✔
41
    const equipmentQuery = useEquipmentQuery();
2✔
42
    const profileQuery = useProfileQuery();
2✔
43

44
    // This could be handled better and more cleanly...
45
    type submissionStatus = 'initial' | 'loading' | 'done';
46
    const [submissionState, setSubmissionState] = useState<submissionStatus>('initial',);
2✔
47

48
    const submitExercise = async () => {
2✔
49

50
        setSubmissionState('loading');
×
51

52
        // Create a new variation object if needed
53
        // TODO: PATCH the other exercise base (newVariationExerciseId) with the new variation id
54
        let variationId;
NEW
55
        if (state.newVariationExerciseId !== null) {
×
56
            variationId = await addVariation();
×
57
        } else {
58
            variationId = state.variationId;
×
59
        }
60

61
        // Create the exercise
62
        const exerciseId = await addExercise(
×
63
            state.category as number,
64
            state.equipment,
65
            state.muscles,
66
            state.musclesSecondary,
67
            variationId,
68
            profileQuery.data!.username
69
        );
70

71
        // Create the English translation
72
        const translation = await addTranslation({
×
73
            exerciseId: exerciseId,
74
            languageId: ENGLISH_LANGUAGE_ID,
75
            name: state.nameEn,
76
            description: state.descriptionEn,
77
            author: profileQuery.data!.username
78
        });
79

80
        // For each entry in alternative names, create a new alias
81
        for (const alias of state.alternativeNamesEn) {
×
82
            await postAlias(translation.id!, alias);
×
83
        }
84

85
        // Post the images
86
        for (const image of state.images) {
×
87
            await postExerciseImage({
×
88
                exerciseId: exerciseId,
89
                image: image.file,
90
                imageData: image,
91
            });
92
        }
93

94
        // Post the notes
95
        for (const note of state.notesEn) {
×
96
            await addNote(new Note(null, translation.id!, note));
×
97
        }
98

99

100
        // Create the translation if needed
101
        if (state.languageId !== null) {
×
102
            const exerciseI18n = await addTranslation({
×
103
                exerciseId: exerciseId,
104
                languageId: state.languageId,
105
                name: state.nameI18n,
106
                description: state.descriptionI18n,
107
                author: profileQuery.data!.username
108
            });
109

110
            for (const alias of state.alternativeNamesI18n) {
×
111
                await postAlias(exerciseI18n.id!, alias);
×
112
            }
113

114
            for (const note of state.notesI18n) {
×
115
                await addNote(new Note(null, exerciseI18n.id!, note));
×
116
            }
117
        }
118

119
        console.log("Exercise created");
×
120
        setSubmissionState('done');
×
121
    };
122

123
    const navigateToOverview = () => {
2✔
124
        navigate(makeLink(WgerLink.EXERCISE_OVERVIEW, i18n.language));
×
125
    };
126

127
    return equipmentQuery.isLoading || languageQuery.isLoading || musclesQuery.isLoading || categoryQuery.isLoading
2!
128
        ? <LoadingPlaceholder />
129
        : <>
130
            <Typography variant={"h6"}>
131
                {t('exercises.step1HeaderBasics')}
132
            </Typography>
133
            <TableContainer>
134
                <Table>
135
                    <TableBody>
136
                        <TableRow>
137
                            <TableCell>{t('name')}</TableCell>
138
                            <TableCell>{state.nameEn}</TableCell>
139
                        </TableRow>
140
                        <TableRow>
141
                            <TableCell>{t('exercises.alternativeNames')}</TableCell>
142
                            <TableCell>{state.alternativeNamesEn.join(", ")}</TableCell>
143
                        </TableRow>
144
                        <TableRow>
145
                            <TableCell>{t('description')}</TableCell>
146
                            <TableCell>{state.descriptionEn}</TableCell>
147
                        </TableRow>
148
                        <TableRow>
149
                            <TableCell>{t('exercises.notes')}</TableCell>
150
                            <TableCell>{state.notesEn.map(note => <>{note}<br /></>)}</TableCell>
×
151
                        </TableRow>
152
                        <TableRow>
153
                            <TableCell>{t('category')}</TableCell>
154
                            <TableCell>{t(getTranslationKey(categoryQuery.data!.find(c => c.id === state.category)!.name))}</TableCell>
2✔
155
                        </TableRow>
156
                        <TableRow>
157
                            <TableCell>{t('exercises.equipment')}</TableCell>
158
                            <TableCell>{state.equipment.map(e => t(getTranslationKey(equipmentQuery.data!.find(value => value.id === e)!.name))).join(', ')}</TableCell>
4✔
159
                        </TableRow>
160
                        <TableRow>
161
                            <TableCell>{t('exercises.muscles')}</TableCell>
162
                            <TableCell>{state.muscles.map(m => musclesQuery.data!.find(value => value.id === m)!.getName(t)).join(', ')}</TableCell>
4✔
163
                        </TableRow>
164
                        <TableRow>
165
                            <TableCell>{t('exercises.secondaryMuscles')}</TableCell>
166
                            <TableCell>{state.musclesSecondary.map(m => musclesQuery.data!.find(value => value.id === m)!.getName(t)).join(', ')}</TableCell>
×
167
                        </TableRow>
168
                        <TableRow>
169
                            <TableCell>{t('exercises.variations')}</TableCell>
170
                            <TableCell>{state.variationId} / {state.newVariationExerciseId}</TableCell>
171
                        </TableRow>
172
                    </TableBody>
173
                </Table>
174
            </TableContainer>
175
            {state.images.length > 0 && (
2!
176
                <ImageList
177
                    cols={3}
178
                    style={{ maxHeight: "200px", }}>
179
                    {state.images.map(imageEntry => (
180
                        <ImageListItem key={imageEntry.url}>
×
181
                            <img
182
                                style={{ maxHeight: "200px", maxWidth: "200px" }}
183
                                src={imageEntry.url}
184
                                alt=""
185
                                loading="lazy"
186
                            />
187
                        </ImageListItem>
188
                    ))}
189
                </ImageList>
190
            )}
191

192

193
            {state.languageId !== null && (
4✔
194
                <>
195
                    <Typography variant={"h6"} sx={{ mt: 3 }}>
196
                        {languageQuery.data!.find(l => l.id === state.languageId)!.nameLong}
6✔
197
                    </Typography>
198
                    <TableContainer>
199
                        <Table>
200
                            <TableBody>
201
                                <TableRow>
202
                                    <TableCell>{t('name')}</TableCell>
203
                                    <TableCell>
204
                                        {state.nameI18n}
205
                                    </TableCell>
206
                                </TableRow>
207

208
                                <TableRow>
209
                                    <TableCell>{t('exercises.alternativeNames')}</TableCell>
210
                                    <TableCell>{state.alternativeNamesI18n.join(", ")}</TableCell>
211
                                </TableRow>
212

213

214
                                <TableRow>
215
                                    <TableCell>{t('description')}</TableCell>
216
                                    <TableCell>{state.descriptionI18n}</TableCell>
217
                                </TableRow>
218

219

220
                                <TableRow>
221
                                    <TableCell>{t('exercises.notes')}</TableCell>
222
                                    <TableCell>{state.notesI18n.map(note => <>{note}<br /></>)}</TableCell>
×
223
                                </TableRow>
224
                            </TableBody>
225
                        </Table>
226
                    </TableContainer>
227
                </>
228
            )}
229

230
            {!(submissionState === 'done')
2!
231
                ? <Alert severity="info" sx={{ mt: 2 }}>
232
                    {t('exercises.checkInformationBeforeSubmitting')}
233
                </Alert>
234
                : <Alert severity="success" sx={{ mt: 2 }}>
235
                    <AlertTitle>{t('success')}</AlertTitle>
236
                    {t('exercises.cacheWarning')}
237
                </Alert>
238
            }
239

240
            <Grid container>
241
                <Grid display="flex" justifyContent={"end"} size={12}>
242
                    <Box sx={{ mb: 2 }}>
243
                        <div>
244
                            {submissionState !== 'done' &&
4✔
245
                                <Button
246
                                    onClick={onBack}
247
                                    sx={{ mt: 1, mr: 1 }}
248
                                >
249
                                    {t('goBack')}
250
                                </Button>
251
                            }
252
                            {submissionState !== 'done'
4✔
253
                                && <Button
254
                                    variant="contained"
255
                                    disabled={submissionState !== 'initial'}
256
                                    onClick={submitExercise}
257
                                    sx={{ mt: 1, mr: 1 }}
258
                                    color="info"
259
                                >
260
                                    {t('exercises.submitExercise')}
261
                                </Button>
262
                            }
263
                            {submissionState === 'done'
2!
264
                                && <Button
265
                                    variant="contained"
266
                                    onClick={navigateToOverview}
267
                                    sx={{ mt: 1, mr: 1 }}
268
                                    color="success"
269
                                >
270
                                    {t('overview')}
271
                                    <NavigateNextIcon />
272
                                </Button>
273
                            }
274
                        </div>
275
                    </Box>
276
                </Grid>
277
            </Grid>
278
        </>;
279
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc