Skip to content

Commit

Permalink
Add products sorting feature
Browse files Browse the repository at this point in the history
  • Loading branch information
alaa-m1 committed Oct 8, 2023
1 parent 24bd2b2 commit e98a042
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 48 deletions.
6 changes: 6 additions & 0 deletions public/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,11 @@
"search_label":"Suchen",
"no_items":"Keine Gegenstände",
"no_categories":"Keine Gegenstände"
},
"sort":{
"sort_by": "Sortiere nach",
"default":"Standard",
"ascending":"Aufsteigend",
"descending":"Absteigend"
}
}
6 changes: 6 additions & 0 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,11 @@
"search_label":"Search",
"no_items":"No items",
"no_categories":"No categories"
},
"sort":{
"sort_by": "Sort by",
"default":"Default",
"ascending":"Ascending",
"descending":"Descending"
}
}
8 changes: 0 additions & 8 deletions src/assets/style/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -425,19 +425,11 @@
padding: 15px 25px;
margin-top: 10px;
}
///////Search panel
.search-panel {
display: flex;
justify-content: center;
padding: 8px 0px;
margin: 5px 0px;
}

.no-items-panel {
display: flex;
justify-content: center;
padding: 10px;
padding: 10px;
margin: 20px;
@include soft-box-shadow;
}
31 changes: 25 additions & 6 deletions src/pages/ClassicCollection/ClassicCollectionDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import { Box, Grid } from "@mui/material";
import { useEffect, useMemo } from "react";
import { useCallback, useEffect, useMemo } from "react";
import { useLocation, useSearchParams } from "react-router-dom";
import { selectMappedCategories } from "store/localProducts/localProductsSelector";
import { useAppSelector } from "utils/redux/hooks";
import {
SearchForProducts,
FilterPanel,
ShopByAllCategories,
ShopByCategory,
ShopNav,
} from "shared/components";
import _ from "lodash";
import { useSortOptions } from "shared";
import { Product } from "types";

const ClassicCollectionDashboard = () => {
const categories = useAppSelector(selectMappedCategories);
const [searchParams] = useSearchParams();
const activeCategoryLabel = searchParams.get("category");
const searchBy = searchParams.get("search");
const sortBy = searchParams.get("sort") ?? "default";

const filteredCategories = useMemo(
() =>
categories.filter((category) =>
Expand All @@ -25,6 +29,20 @@ const ClassicCollectionDashboard = () => {
),
[categories, searchBy]
);

const comparisonFn = useCallback(
(a: Product, b: Product) => {
if (sortBy === "asc") return a.price - b.price;
return b.price - a.price;
},
[sortBy]
);

const SortedCategories =
sortBy === "default"
? filteredCategories
: _.cloneDeep(filteredCategories).sort(comparisonFn);

const mainCategoriesLabels = useMemo(
() =>
filteredCategories.reduce<Array<string>>((res, category) => {
Expand All @@ -38,19 +56,20 @@ const ClassicCollectionDashboard = () => {

const activeCategoryItems = useMemo(
() =>
filteredCategories.filter(
SortedCategories.filter(
(cat, index) => cat.categoryLabel === activeCategoryLabel
),
[filteredCategories, activeCategoryLabel]
[SortedCategories, activeCategoryLabel]
);

const sortOptions = useSortOptions();
return (
<Box>
<ShopNav
mainCategoriesLabels={mainCategoriesLabels}
activeCategoryLabel={activeCategoryLabel ?? ""}
/>
<SearchForProducts />
<FilterPanel sortOptions={sortOptions} />
<Grid container>
<Grid
item
Expand All @@ -69,7 +88,7 @@ const ClassicCollectionDashboard = () => {
) : (
<ShopByAllCategories
mainCategoriesLabels={mainCategoriesLabels}
categories={filteredCategories}
categories={SortedCategories}
/>
)}
</Grid>
Expand Down
31 changes: 23 additions & 8 deletions src/pages/ModernCollection/ModernCollectionDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Box, Grid } from "@mui/material";
import { useEffect, useMemo } from "react";
import { useCallback, useEffect, useMemo } from "react";
import { useSearchParams } from "react-router-dom";
import { useAppSelector } from "utils/redux/hooks";
import {
Expand All @@ -11,20 +11,22 @@ import {
setProducts,
} from "store/products/productsActions";
import { useDispatch } from "react-redux";
import { FullScreenSpinner } from "shared";
import { FullScreenSpinner, useSortOptions } from "shared";
import {
SearchForProducts,
FilterPanel,
ShopByAllCategories,
ShopByCategory,
ShopNav,
} from "shared/components";
import { useProducts } from "./hooks";
import _ from "lodash";
import { Product } from "types";

const ModernCollectionDashboard = () => {
const [searchParams] = useSearchParams();
const activeCategoryLabel = searchParams.get("category");
const searchBy = searchParams.get("search");
const sortBy = searchParams.get("sort") ?? "default";
const dispatch = useDispatch();

/// Using react-query to manage request state with caching (The other good solution to use with Redux is RTK Query)
Expand Down Expand Up @@ -52,6 +54,19 @@ const ModernCollectionDashboard = () => {
[onlineCategories, searchBy]
);

const comparisonFn = useCallback(
(a: Product, b: Product) => {
if (sortBy === "asc") return a.price - b.price;
return b.price - a.price;
},
[sortBy]
);

const SortedCategories =
sortBy === "default"
? filteredCategories
: _.cloneDeep(filteredCategories).sort(comparisonFn);

const loading = useMemo(
() => isLoading || status.loading,
[isLoading, status.loading]
Expand All @@ -70,19 +85,19 @@ const ModernCollectionDashboard = () => {

const activeCategoryItems = useMemo(
() =>
filteredCategories.filter(
SortedCategories.filter(
(cat, index) => cat.categoryLabel === activeCategoryLabel
),
[filteredCategories, activeCategoryLabel]
[SortedCategories, activeCategoryLabel]
);

const sortOptions = useSortOptions();
return (
<Box>
<ShopNav
mainCategoriesLabels={mainCategoriesLabels}
activeCategoryLabel={activeCategoryLabel ?? ""}
/>
<SearchForProducts />
<FilterPanel sortOptions={sortOptions} />
<Grid container sx={{ position: "relative" }}>
<Grid
item
Expand All @@ -101,7 +116,7 @@ const ModernCollectionDashboard = () => {
) : (
<ShopByAllCategories
mainCategoriesLabels={mainCategoriesLabels}
categories={filteredCategories}
categories={SortedCategories}
/>
)}
</Grid>
Expand Down
32 changes: 32 additions & 0 deletions src/shared/components/FilterPanel/FilterPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Grid, useTheme } from "@mui/material";
import { SearchForProducts } from "./SearchForProducts";
import { SortTypeSelect } from "./SortTypeSelect";
import { SortOptions } from "types";

export const FilterPanel = ({ sortOptions }: FilterPanelProps) => {
const theme=useTheme()
return (
<Grid container columnSpacing={2} mt={1}>
<Grid
item
xs={12}
md={6}
sx={{ display: "flex", justifyContent: "flex-end", [theme.breakpoints.down('md')]:{justifyContent: "center"} }}
>
<SearchForProducts />
</Grid>
<Grid
item
xs={12}
md={6}
sx={{ display: "flex", justifyContent: "flex-start", [theme.breakpoints.down('md')]:{justifyContent: "center"} }}
>
<SortTypeSelect options={sortOptions} />
</Grid>
</Grid>
);
};

type FilterPanelProps = {
sortOptions: SortOptions;
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Box, IconButton, Stack, TextField } from "@mui/material";
import { Box, IconButton } from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import { useCallback, useMemo, useState } from "react";
import _ from "lodash";
import { FormControl } from "@mui/material";
import { InputLabel } from "@mui/material";
import { Input } from "@mui/material";
import { InputAdornment } from "@mui/material";

export const SearchForProducts = () => {
const { t } = useTranslation();
let [searchParams, setSearchParams] = useSearchParams();
const [searchParams, setSearchParams] = useSearchParams();

const [searchValue, setSearchValue] = useState(() => {
const searchValue = searchParams.get("search");
Expand All @@ -28,8 +32,13 @@ export const SearchForProducts = () => {
const searchDebounceFn = useMemo(
() =>
_.debounce((searchValue: string) => {
searchParams.set("search", searchValue);
setSearchParams(searchParams);
if (_.isEmpty(searchValue)) {
if (searchParams.has("search")) searchParams.delete("search");
setSearchParams(searchParams);
} else {
searchParams.set("search", searchValue);
setSearchParams(searchParams);
}
}, 1000),
[searchParams, setSearchParams]
);
Expand All @@ -46,7 +55,8 @@ export const SearchForProducts = () => {
if (_.isEmpty(newSearch)) {
if (searchParams.has("search")) searchParams.delete("search");
setSearchParams(searchParams);
} else handleDebouncedSearchChange(newSearch);
}
handleDebouncedSearchChange(newSearch);
},
[handleDebouncedSearchChange, searchParams, setSearchParams]
);
Expand All @@ -66,26 +76,28 @@ export const SearchForProducts = () => {
},
[searchParams, setSearchParams]
);

return (
<Box className="search-panel">
<TextField
id="supplier_dashboard_textfield_search"
placeholder={t("search.search_for_products")}
size="small"
variant="standard"
type="search"
InputProps={{
endAdornment: (
<IconButton size="small" onClick={handleApplySearch}>
<SearchIcon sx={{ path: { color: "secondary.main" } }} />
</IconButton>
),
}}
value={searchValue}
onChange={handleSearchChange}
onKeyDown={handleKeyDown}
/>
<Box>
<FormControl variant="standard" sx={{ m: 1, minWidth: 200 }}>
<InputLabel htmlFor="search_box_id">
{t("search.search_for_products")}:
</InputLabel>
<Input
id="search_box_id"
type="search"
startAdornment={<InputAdornment position="end"></InputAdornment>}
endAdornment={
<InputAdornment position="end">
<IconButton size="small" onClick={handleApplySearch}>
<SearchIcon sx={{ path: { color: "secondary.main" } }} />
</IconButton>
</InputAdornment>
}
value={searchValue}
onChange={handleSearchChange}
onKeyDown={handleKeyDown}
/>
</FormControl>
</Box>
);
};
50 changes: 50 additions & 0 deletions src/shared/components/FilterPanel/SortTypeSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { FormControl, InputLabel, MenuItem, Select, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import _ from "lodash";
import { useMemo } from "react";
import { SortOptions } from "types";
import { useSearchParams } from "react-router-dom";

export const SortTypeSelect = (props: SortTypeSelectProps) => {
const { t } = useTranslation();
const [searchParams, setSearchParams] = useSearchParams();

const SelectMenuItems = useMemo(
() =>
props.options.map((item, index) => (
<MenuItem
value={item.value}
key={_.uniqueId()}
id={`sort_by_${item.value}`}
>
<Typography color="primary.light">
{item.label}
</Typography>
</MenuItem>
)),
[props.options]
);

return (
<FormControl variant="standard" sx={{ m: 1, minWidth: 200 }}>
<InputLabel htmlFor="sort_select_id" disableAnimation>
{t("sort.sort_by")}:
</InputLabel>
<Select
id="sort_select_id"
value={searchParams.get("sort") || "default"}
onChange={(e) => {
searchParams.set("sort", e.target.value as string);
setSearchParams(searchParams);
}}
label={t("sort.sort_by")}
>
{SelectMenuItems}
</Select>
</FormControl>
);
};

type SortTypeSelectProps = {
options: SortOptions;
};
2 changes: 1 addition & 1 deletion src/shared/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export * from './LoadingSpinner';
export * from './FullScreenSpinner';
export * from './StyledLink';
export * from './Categories';
export * from './SearchForProducts';
export * from './FilterPanel/FilterPanel';
Loading

0 comments on commit e98a042

Please sign in to comment.