Skip to content

Commit

Permalink
Merge pull request #92 from aodn/features/5715-add-sorting
Browse files Browse the repository at this point in the history
Add sorting
  • Loading branch information
utas-raymondng authored Jul 17, 2024
2 parents 092c893 + 1a970c5 commit 4dfa030
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 60 deletions.
56 changes: 30 additions & 26 deletions src/components/common/buttons/MapListToggleButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IconButton, ListItemIcon, MenuItem } from "@mui/material";
import ArrowDropDownSharpIcon from "@mui/icons-material/ArrowDropDownSharp";
import React, { useState } from "react";
import React, { FC, useCallback, useState } from "react";
import Menu from "@mui/material/Menu";
import ActionButtonPaper from "./ActionButtonPaper";
import GridAndMapIcon from "../../icon/GridAndMapIcon";
Expand All @@ -14,53 +14,57 @@ enum SearchResultLayoutEnum {
VISIBLE = "VISIBLE",
}

interface MapListToggleButtonProps {
export interface MapListToggleButtonProps {
onChangeLayout: (layout: SearchResultLayoutEnum) => void;
}

const MapListToggleButton = ({ onChangeLayout }: MapListToggleButtonProps) => {
const determineShowingIcon = (resultLayout: SearchResultLayoutEnum) => {
switch (resultLayout) {
case SearchResultLayoutEnum.LIST:
return <ListAndMapIcon />;

case SearchResultLayoutEnum.GRID:
return <GridAndMapIcon />;

default:
return <FullMapViewIcon />;
}
};

const MapListToggleButton: FC<MapListToggleButtonProps> = ({
onChangeLayout,
}) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [resultLayout, setResultLayout] = useState<SearchResultLayoutEnum>(
SearchResultLayoutEnum.GRID
);
const open = Boolean(anchorEl);

const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
},
[setAnchorEl]
);

const handleClose = () => {
const handleClose = useCallback(() => {
setAnchorEl(null);
};

const determineShowingIcon = () => {
switch (resultLayout) {
case SearchResultLayoutEnum.LIST:
return <ListAndMapIcon />;

case SearchResultLayoutEnum.GRID:
return <GridAndMapIcon />;

default:
return <FullMapViewIcon />;
}
};
}, [setAnchorEl]);

return (
<ActionButtonPaper>
<IconButton
id="map-list-toggle-button"
onClick={handleClick}
aria-controls={open ? "map-list-toggle-menu" : undefined}
aria-controls={anchorEl != null ? "map-list-toggle-menu" : undefined}
aria-haspopup="true"
aria-expanded={open ? "true" : undefined}
aria-expanded={anchorEl != null ? "true" : undefined}
data-testid="map-list-toggle-button"
>
{determineShowingIcon()}
{determineShowingIcon(resultLayout)}
<ArrowDropDownSharpIcon />
</IconButton>
<Menu
open={open}
open={anchorEl != null}
onClose={handleClose}
id="map-list-toggle-menu"
anchorEl={anchorEl}
Expand Down
130 changes: 125 additions & 5 deletions src/components/common/buttons/SortButton.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,138 @@
import { IconButton } from "@mui/material";
import { IconButton, ListItemIcon, Menu, MenuItem } from "@mui/material";
import SubtitlesIcon from "@mui/icons-material/Subtitles";
import TryIcon from "@mui/icons-material/Try";
import EditNoteIcon from "@mui/icons-material/EditNote";
import ArrowDropDownSharpIcon from "@mui/icons-material/ArrowDropDownSharp";
import React from "react";
import ActionButtonPaper from "./ActionButtonPaper";
import RelevancyIcon from "../../icon/RelevancyIcon";
import { FC, useCallback, useState } from "react";

enum SortResultEnum {
RELEVANT = "RELEVANT",
TITLE = "TITLE",
POPULARITY = "POPULARITY",
MODIFIED = "MODIFIED",
}

export interface SortButtonProps {
onChangeSorting: (layout: SortResultEnum) => void;
}

const determineShowingIcon = (resultLayout: SortResultEnum) => {
switch (resultLayout) {
case SortResultEnum.RELEVANT:
return <RelevancyIcon />;

case SortResultEnum.TITLE:
return <SubtitlesIcon />;

case SortResultEnum.POPULARITY:
return <TryIcon />;

case SortResultEnum.MODIFIED:
return <EditNoteIcon />;

default:
return <RelevancyIcon />;
}
};

const SortButton: FC<SortButtonProps> = ({ onChangeSorting }) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [resultLayout, setResultLayout] = useState<SortResultEnum>(
SortResultEnum.RELEVANT
);

const handleClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
},
[setAnchorEl]
);

const handleClose = useCallback(() => {
setAnchorEl(null);
}, [setAnchorEl]);

const SortButton = () => {
return (
<ActionButtonPaper>
<IconButton>
<RelevancyIcon />
<IconButton
id="sort-list-toggle-button"
onClick={handleClick}
aria-controls={anchorEl != null ? "sort-list-toggle-menu" : undefined}
aria-haspopup="true"
aria-expanded={anchorEl != null ? "true" : undefined}
data-testid="sort-list-toggle-button"
>
{determineShowingIcon(resultLayout)}
<ArrowDropDownSharpIcon />
</IconButton>
<Menu
open={anchorEl != null}
onClose={handleClose}
id="sort-list-toggle-menu"
anchorEl={anchorEl}
MenuListProps={{
"aria-labelledby": "sort-list-toggle-button",
}}
>
<MenuItem
data-testid="sortlist-toggle-menu-relevant"
onClick={() => {
// No need to set layout because we want to
// remember the last layout
handleClose();
onChangeSorting(SortResultEnum.RELEVANT);
}}
>
<ListItemIcon>
<RelevancyIcon />
</ListItemIcon>
Relevancy
</MenuItem>
<MenuItem
data-testid="sortlist-toggle-menu-title"
onClick={() => {
setResultLayout(SortResultEnum.TITLE);
handleClose();
onChangeSorting(SortResultEnum.TITLE);
}}
>
<ListItemIcon>
<SubtitlesIcon />
</ListItemIcon>
Title
</MenuItem>
<MenuItem
data-testid="sortlist-toggle-menu-popularity"
onClick={() => {
setResultLayout(SortResultEnum.POPULARITY);
handleClose();
onChangeSorting(SortResultEnum.POPULARITY);
}}
>
<ListItemIcon>
<TryIcon />
</ListItemIcon>
Popularity
</MenuItem>
<MenuItem
data-testid="sortlist-toggle-menu-modified"
onClick={() => {
setResultLayout(SortResultEnum.MODIFIED);
handleClose();
onChangeSorting(SortResultEnum.MODIFIED);
}}
>
<ListItemIcon>
<EditNoteIcon />
</ListItemIcon>
Modified
</MenuItem>
</Menu>
</ActionButtonPaper>
);
};

export { SortResultEnum };
export default SortButton;
23 changes: 10 additions & 13 deletions src/components/common/filters/ResultPanelSimpleFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,25 @@ import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos";

import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
import MapListToggleButton, {
SearchResultLayoutEnum,
MapListToggleButtonProps,
} from "../buttons/MapListToggleButton";
import SortButton from "../buttons/SortButton";
import React from "react";

// interface ResultPanelSimpleFilterProps {
// filterClicked?: (e: React.MouseEvent<HTMLButtonElement>) => void;
// }
import SortButton, { SortButtonProps } from "../buttons/SortButton";
import { FC } from "react";

const SlimSelect = styled(InputBase)(() => ({
"& .MuiInputBase-input": {
border: "none",
},
}));

interface ResultPanelSimpleFilterProps {
onChangeLayout: (layout: SearchResultLayoutEnum) => void;
}
interface ResultPanelSimpleFilterProps
extends MapListToggleButtonProps,
SortButtonProps {}

const ResultPanelSimpleFilter = ({
const ResultPanelSimpleFilter: FC<ResultPanelSimpleFilterProps> = ({
onChangeLayout,
}: ResultPanelSimpleFilterProps) => {
onChangeSorting,
}) => {
return (
<Grid container justifyContent="center">
<Grid container item xs={12} sx={{ pb: 1 }}>
Expand Down Expand Up @@ -62,7 +59,7 @@ const ResultPanelSimpleFilter = ({
<ArrowForwardIosIcon />
</IconButton>
</Paper>
<SortButton />
<SortButton onChangeSorting={onChangeSorting} />
<MapListToggleButton onChangeLayout={onChangeLayout} />
</Grid>
</Grid>
Expand Down
24 changes: 24 additions & 0 deletions src/components/common/store/componentParamReducer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
import { bboxPolygon } from "@turf/turf";
import { Feature, Polygon, GeoJsonProperties } from "geojson";
import { sortBy } from "lodash";

const UPDATE_PARAMETER_STATES = "UPDATE_PARAMETER_STATES";
const UPDATE_DATETIME_FILTER_VARIABLE = "UPDATE_DATETIME_FILTER_VARIABLE";
Expand All @@ -12,6 +13,7 @@ const UPDATE_IMOS_ONLY_DATASET_FILTER_VARIABLE =
"UPDATE_IMOS_ONLY_DATASET_FILTER_VARIABLE";
const UPDATE_POLYGON_FILTER_VARIABLE = "UPDATE_POLYGON_FILTER_VARIABLE";
const UPDATE_CATEGORY_FILTER_VARIABLE = "UPDATE_CATEGORY_FILTER_VARIABLE";
const UPDATE_SORT_BY_VARIABLE = "UPDATE_SORT_BY_VARIABLE";

interface DataTimeFilterRange {
// Cannot use Date in Redux as it is non-serializable
Expand All @@ -27,6 +29,7 @@ export interface ParameterState {
// Use in search box
searchText?: string;
categories?: Array<Category>;
sortby?: string;
}
// Function use to test an input value is of type Category
const isTypeCategory = (value: any): value is Category =>
Expand Down Expand Up @@ -96,6 +99,21 @@ const updateCategories = (input: Array<Category>): ActionType => {
};
};

const updateSortBy = (
input: Array<{ field: string; order: "ASC" | "DESC" }>
): ActionType => {
return {
type: UPDATE_SORT_BY_VARIABLE,
payload: {
sortby: input
.map((item) =>
item.order === "ASC" ? `+${item.field}` : `-${item.field}`
)
.join(","),
},
};
};

// Initial State
const createInitialParameterState = (): ParameterState => {
return {
Expand Down Expand Up @@ -139,6 +157,11 @@ const paramReducer = (
...state,
categories: action.payload.categories,
};
case UPDATE_SORT_BY_VARIABLE:
return {
...state,
sortby: action.payload.sortby,
};
case UPDATE_PARAMETER_STATES:
return {
...state,
Expand Down Expand Up @@ -264,4 +287,5 @@ export {
updateFilterPolygon,
updateCategories,
updateParameterStates,
updateSortBy,
};
7 changes: 7 additions & 0 deletions src/components/common/store/searchReducer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ export type SearchParameters = {
text?: string;
filter?: string;
properties?: string;
sortby?: string;
};

type OGCSearchParameters = {
q?: string;
filter?: string;
properties?: string;
sortby?: string;
};

export interface CollectionsQueryType {
Expand Down Expand Up @@ -90,6 +92,10 @@ const searchResult = async (param: SearchParameters, thunkApi: any) => {
p.filter = `score>=${DEFAULT_SEARCH_SCORE}`;
}

if (param.sortby !== undefined && param.sortby.length !== 0) {
p.sortby = param.sortby;
}

const response = await axios.get<OGCCollections>(
"/api/v1/ogc/collections",
{
Expand Down Expand Up @@ -243,6 +249,7 @@ const createSearchParamFrom = (i: ParameterState): SearchParameters => {
const p: SearchParameters = {};
p.text = i.searchText;
p.filter = undefined;
p.sortby = i.sortby;

if (i.isImosOnlyDataset) {
p.filter = appendFilter(
Expand Down
Loading

0 comments on commit 4dfa030

Please sign in to comment.