Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a drawer with filters for mobile use on exercise overview page #893

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"unit": "Unit",
"alsoSearchEnglish": "Also search for names in English",
"copyToClipboard": "Copy to clipboard",
"filters": "Filters",
"exercises": {
"replacements": "Replacements",
"replacementsInfoText": "Optionally, you can also select an exercise that should replace this one (e.g. because it was submitted twice, or similar). This will replace the exercise in routines as well as training logs, instead of just deleting it. These changes will also propagate to any instance that syncs the exercises from this one.",
Expand Down
202 changes: 113 additions & 89 deletions src/components/Exercises/ExerciseOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
import AddIcon from '@mui/icons-material/Add';
import { Box, Button, Container, Grid, Pagination, Stack, Typography, } from "@mui/material";
import { LoadingPlaceholder } from "components/Core/LoadingWidget/LoadingWidget";
import { CategoryFilter } from "components/Exercises/Filter/CategoryFilter";
import { EquipmentFilter } from "components/Exercises/Filter/EquipmentFilter";
import { MuscleFilter } from "components/Exercises/Filter/MuscleFilter";
import { Box, Button, Container, Grid, Pagination, Stack, Typography, useMediaQuery } from "@mui/material";
import { CategoryFilter, CategoryFilterDropdown } from "components/Exercises/Filter/CategoryFilter";
import { EquipmentFilter, EquipmentFilterDropdown } from "components/Exercises/Filter/EquipmentFilter";
import { MuscleFilter, MuscleFilterDropdown } from "components/Exercises/Filter/MuscleFilter";
import { NameAutocompleter } from "components/Exercises/Filter/NameAutcompleter";
import { Category } from "components/Exercises/models/category";
import { Equipment } from "components/Exercises/models/equipment";
import { Muscle } from "components/Exercises/models/muscle";
import { ExerciseGrid } from "components/Exercises/Overview/ExerciseGrid";
import { ExerciseGridSkeleton } from "components/Exercises/Overview/ExerciseGridLoadingSkeleton";
import {
useCategoriesQuery,
useEquipmentQuery,
useExercisesQuery,
useMusclesQuery
} from "components/Exercises/queries";
import React from "react";
import { useExercisesQuery } from "components/Exercises/queries";
import React, { useContext, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import { ExerciseSearchResponse } from "services/responseType";
import { makeLink, WgerLink } from "utils/url";
import { FilterDrawer } from './Filter/FilterDrawer';
import { ExerciseFiltersContext } from './Filter/ExerciseFiltersContext';

const ContributeExerciseBanner = () => {
const [t, i18n] = useTranslation();
Expand Down Expand Up @@ -74,17 +70,12 @@ const NoResultsBanner = () => {
);
};

export const ExerciseOverview = () => {
export const ExerciseOverviewList = () => {
const basesQuery = useExercisesQuery();
const categoryQuery = useCategoriesQuery();
const musclesQuery = useMusclesQuery();
const equipmentQuery = useEquipmentQuery();
const [t, i18n] = useTranslation();
const navigate = useNavigate();

const [selectedEquipment, setSelectedEquipment] = React.useState<Equipment[]>([]);
const [selectedMuscles, setSelectedMuscles] = React.useState<Muscle[]>([]);
const [selectedCategories, setSelectedCategories] = React.useState<Category[]>([]);
const { selectedCategories, selectedEquipment, selectedMuscles} = useContext(ExerciseFiltersContext);
const isMobile = useMediaQuery('(max-width:600px)');

const [page, setPage] = React.useState(1);
const handlePageChange = (event: any, value: number) => {
Expand All @@ -96,40 +87,44 @@ export const ExerciseOverview = () => {
});
};

let filteredExercises = useMemo(() => {
let filteredExercises = basesQuery.data || [];

// Filter exercise bases by categories
if (selectedCategories.length > 0) {
filteredExercises = filteredExercises!.filter(exercise => {
return selectedCategories.some(
category => exercise.category.id === category.id
);
});
}

// Filter exercises that have one of the selected equipment
if (selectedEquipment.length > 0) {
filteredExercises = filteredExercises!.filter(exercise => {
return exercise.equipment.some(equipment =>
selectedEquipment.some(
selectedEquipment => selectedEquipment.id === equipment.id
)
);
});
}

// Filter exercises that have one of the selected muscles
if (selectedMuscles.length > 0) {
filteredExercises = filteredExercises!.filter(exercise => {
return exercise.muscles.some(muscle =>
selectedMuscles.some(selectedMuscle => selectedMuscle.id === muscle.id)
);
});
}

return filteredExercises;
}, [basesQuery.data, selectedCategories, selectedEquipment, selectedMuscles]);

// Should be a multiple of three, since there are three columns in the grid
const ITEMS_PER_PAGE = 21;

let filteredExercises = basesQuery.data || [];

// Filter exercise bases by categories
if (selectedCategories.length > 0) {
filteredExercises = filteredExercises!.filter(exercise => {
return selectedCategories.some(
category => exercise.category.id === category.id
);
});
}

// Filter exercises that have one of the selected equipment
if (selectedEquipment.length > 0) {
filteredExercises = filteredExercises!.filter(exercise => {
return exercise.equipment.some(equipment =>
selectedEquipment.some(
selectedEquipment => selectedEquipment.id === equipment.id
)
);
});
}

// Filter exercises that have one of the selected muscles
if (selectedMuscles.length > 0) {
filteredExercises = filteredExercises!.filter(exercise => {
return exercise.muscles.some(muscle =>
selectedMuscles.some(selectedMuscle => selectedMuscle.id === muscle.id)
);
});
}

// Pagination calculations
const pageCount = Math.ceil(filteredExercises!.length / ITEMS_PER_PAGE);
const paginatedExercises = filteredExercises!.slice(
Expand All @@ -144,57 +139,67 @@ export const ExerciseOverview = () => {
return (
<Container maxWidth="lg">
<Grid container spacing={2} mt={2}>
<Grid item xs={12} sm={6}>
<Grid item xs={10} sm={6}>
<Typography gutterBottom variant="h3" component="div">
{t("exercises.exercises")}
</Typography>
</Grid>
<Grid item xs={12} sm={3}>
<NameAutocompleter callback={exerciseAdded} />
</Grid>
<Grid item xs={12} sm={3}>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => navigate(makeLink(WgerLink.EXERCISE_CONTRIBUTE, i18n.language))}
>
{t('exercises.contributeExercise')}
</Button>
</Grid>

<Grid item xs={12} sm={3}>
<Grid container spacing={1}>
{categoryQuery.isLoading ? <LoadingPlaceholder /> : (
{isMobile ? (
<>
<Grid item xs={2} sm={6}>
<Button
variant="contained"
onClick={() => navigate(makeLink(WgerLink.EXERCISE_CONTRIBUTE, i18n.language))}
>
<AddIcon />
</Button>
</Grid>
<Grid item sm={6} flexGrow={1}>
<NameAutocompleter callback={exerciseAdded} />
</Grid>
<Grid item xs={2} sm={6} display="flex" justifyContent="center" alignItems="center">
<FilterDrawer>
<CategoryFilterDropdown />
<EquipmentFilterDropdown />
<MuscleFilterDropdown />
</FilterDrawer>
</Grid>
</>
) : (
<>
<Grid item xs={12} sm={3}>
<NameAutocompleter callback={exerciseAdded} />
</Grid>
<Grid item xs={12} sm={3}>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => navigate(makeLink(WgerLink.EXERCISE_CONTRIBUTE, i18n.language))}
>
{t('exercises.contributeExercise')}
</Button>
</Grid>
</>
)}

{!isMobile && (
<Grid item xs={12} sm={3}>
<Grid container spacing={1}>
<Grid item xs={6} sm={12}>
<CategoryFilter
categories={categoryQuery.data!}
selectedCategories={selectedCategories}
setSelectedCategories={setSelectedCategories}
/>
<CategoryFilter />
</Grid>
)}

{equipmentQuery.isLoading ? <LoadingPlaceholder /> : (
<Grid item xs={6} sm={12}>
<EquipmentFilter
equipment={equipmentQuery.data!}
selectedEquipment={selectedEquipment}
setSelectedEquipment={setSelectedEquipment}
/>
<EquipmentFilter />
</Grid>
)}

{musclesQuery.isLoading ? <LoadingPlaceholder /> : (
<Grid item xs={12}>
<MuscleFilter
muscles={musclesQuery.data!}
selectedMuscles={selectedMuscles}
setSelectedMuscles={setSelectedMuscles}
/>
<MuscleFilter />
</Grid>
)}
</Grid>
</Grid>
</Grid>
)}

<Grid item xs={12} sm={9}>
{basesQuery.isLoading
? <ExerciseGridSkeleton />
Expand All @@ -217,3 +222,22 @@ export const ExerciseOverview = () => {
</Container>
);
};

export const ExerciseOverview = () => {
const [selectedEquipment, setSelectedEquipment] = useState<Equipment[]>([]);
const [selectedMuscles, setSelectedMuscles] = useState<Muscle[]>([]);
const [selectedCategories, setSelectedCategories] = React.useState<Category[]>([]);

return (
<ExerciseFiltersContext.Provider value={{
selectedEquipment,
setSelectedEquipment,
selectedMuscles,
setSelectedMuscles,
selectedCategories,
setSelectedCategories
}}>
<ExerciseOverviewList />
</ExerciseFiltersContext.Provider>
);
};
Loading