Skip to content

Commit

Permalink
✨Sort modules in home overview (#264)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rllyyy committed Apr 13, 2023
1 parent 4dcbb9f commit a393f53
Show file tree
Hide file tree
Showing 9 changed files with 342 additions and 40 deletions.
19 changes: 8 additions & 11 deletions src/components/Home/AddModule.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,23 @@

line-height: 1;
text-align: center;
white-space: nowrap;

background-color: var(--custom-prime-color);
color: whitesmoke;
cursor: pointer;

margin-left: 5px;
padding: 10px 0;

width: 100%;
max-width: 118px;
padding: 10px 6px;

border-radius: 5px;
}

.add-module-btn:focus-visible {
outline: 2px solid var(--custom-prime-color-dark);
outline-offset: 4px;
background-color: var(--custom-prime-color-dark);
}

.add-module-btn p {
font-weight: 500;
font-size: 18px;
Expand Down Expand Up @@ -54,12 +57,6 @@
cursor: pointer;
}

/* .import-create-module h2 {
text-align: start;
width: 100%;
margin-bottom: 0.5rem;
} */

.import-module {
display: flex;
align-items: center;
Expand Down
2 changes: 1 addition & 1 deletion src/components/Home/CreateModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export const CreateModule = ({ handleModalClose }: ICreateModule) => {
<select
id='create-module-language-select'
{...register("lang", {
required: "Select a language for the module..",
required: "Select a language for the module.",
})}
className={`${errors.lang ? "is-invalid" : ""}`}
>
Expand Down
12 changes: 12 additions & 0 deletions src/components/Home/Home.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.sort-add-module-container {
display: flex;
gap: 6px;
margin-left: 4px;
}

@media only screen and (max-width: 650px) {
.sort-add-module-container {
flex-direction: column-reverse;
align-items: flex-end;
}
}
141 changes: 141 additions & 0 deletions src/components/Home/Home.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,147 @@ const MockModulesWithRouter = () => {
};

describe("Modules (Home) component", () => {
it("should default select the sort by name (ascending)", () => {
cy.mount(<MockModulesWithRouter />);

cy.contains("button", "Sort").click();
cy.contains("Name (ascending)").should("exist").and("be.selected");
});

it("should change the selected sort on click", () => {
cy.mount(<MockModulesWithRouter />);

cy.contains("button", "Sort").click();
cy.contains("ID (ascending)").click();

cy.contains("button", "Sort").click();
cy.contains("ID (ascending)").should("be.selected");
});

it("should default sort the modules by name ascending", () => {
// Add Modules to localStorage
cy.fixtureToLocalStorage("repeatio-module-cypress_1.json");
cy.fixtureToLocalStorage("repeatio-module-gap_text.json");
cy.fixtureToLocalStorage("repeatio-module-gap_text_dropdown.json");
cy.fixtureToLocalStorage("repeatio-module-multiple_choice.json");
cy.fixtureToLocalStorage("repeatio-module-multiple_response.json");

cy.mount(<MockModulesWithRouter />);
// get order of all items
cy.get("article")
.then((modules) => modules.get().map((module) => module.getAttribute("data-cy")))
.should("deep.equal", [
"module-cypress_1",
"module-types_1",
"module-gap_text_dropdown",
"module-gap_text",
"module-multiple_choice",
"module-multiple_response",
]);
});

it("should show the modules in reverse order of name on sort Name (descending) click", () => {
// Add Modules to localStorage
cy.fixtureToLocalStorage("repeatio-module-cypress_1.json");
cy.fixtureToLocalStorage("repeatio-module-gap_text.json");
cy.fixtureToLocalStorage("repeatio-module-gap_text_dropdown.json");
cy.fixtureToLocalStorage("repeatio-module-multiple_choice.json");
cy.fixtureToLocalStorage("repeatio-module-multiple_response.json");

cy.mount(<MockModulesWithRouter />);

cy.contains("button", "Sort").click();
cy.contains("Name (descending)").click();

// get order of all items
cy.get("article")
.then((modules) => modules.get().map((module) => module.getAttribute("data-cy")))
.should("deep.equal", [
"module-multiple_response",
"module-multiple_choice",
"module-gap_text",
"module-gap_text_dropdown",
"module-types_1",
"module-cypress_1",
]); //
});

it("should sort the modules by id ascending on select click", () => {
// Add Modules to localStorage
cy.fixtureToLocalStorage("repeatio-module-cypress_1.json");
cy.fixtureToLocalStorage("repeatio-module-gap_text.json");
cy.fixtureToLocalStorage("repeatio-module-gap_text_dropdown.json");
cy.fixtureToLocalStorage("repeatio-module-multiple_choice.json");
cy.fixtureToLocalStorage("repeatio-module-multiple_response.json");

cy.mount(<MockModulesWithRouter />);

cy.contains("button", "Sort").click();
cy.contains("ID (ascending)").click();

//cy.wait(200);

// get order of all items
cy.get("article")
.then((modules) => modules.get().map((module) => module.getAttribute("data-cy")))
.should("deep.equal", [
"module-cypress_1",
"module-gap_text",
"module-gap_text_dropdown",
"module-multiple_choice",
"module-multiple_response",
"module-types_1",
]);
});

it("should sort the modules by id descending on select click", () => {
// Add Modules to localStorage
cy.fixtureToLocalStorage("repeatio-module-cypress_1.json");
cy.fixtureToLocalStorage("repeatio-module-gap_text.json");
cy.fixtureToLocalStorage("repeatio-module-gap_text_dropdown.json");
cy.fixtureToLocalStorage("repeatio-module-multiple_choice.json");
cy.fixtureToLocalStorage("repeatio-module-multiple_response.json");

cy.mount(<MockModulesWithRouter />);

cy.contains("button", "Sort").click();
cy.contains("ID (descending)").click();

// get order of all items
cy.get("article")
.then((modules) => modules.get().map((module) => module.getAttribute("data-cy")))
.should("deep.equal", [
"module-types_1",
"module-multiple_response",
"module-multiple_choice",
"module-gap_text_dropdown",
"module-gap_text",
"module-cypress_1",
]);
});

it("should sort a added module ", () => {
cy.viewport(800, 700);

// Add Modules to localStorage
cy.fixtureToLocalStorage("repeatio-module-cypress_1.json");
cy.mount(<MockModulesWithRouter />);

cy.contains("button", "Add Module").click();
cy.contains("label", "Create Module").click();

cy.get("input[name='id']").type("Y");
cy.get("input[name='name']").type("Y");

cy.get("select[name=lang]").select("en");
cy.contains("button", "Create").click();

// Assert that the new module was added to the end
cy.get("article")
.then((modules) => modules.get().map((module) => module.getAttribute("data-cy")))
.should("deep.equal", ["module-cypress_1", "module-types_1", "module-Y"]);
});

it("should show modal if clicking on the 'Add Module' button", () => {
cy.viewport(500, 500);
cy.mount(<MockModulesWithRouter />);
Expand Down
90 changes: 90 additions & 0 deletions src/components/Home/ModuleSortButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { MdSort } from "react-icons/md";
import { useState } from "react";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import List from "@mui/material/List";

const moduleSortOptions = ["Name (ascending)", "Name (descending)", "ID (ascending)", "ID (descending)"] as const;

export type TModuleSortOption = typeof moduleSortOptions[number];

interface ISortButton {
sort: TModuleSortOption;
setSort: React.Dispatch<React.SetStateAction<TModuleSortOption>>;
}

/* Source: https://codesandbox.io/s/0e67co?file=/demo.tsx and https://mui.com/material-ui/react-menu/*/
export const SortButton: React.FC<ISortButton> = ({ sort, setSort }) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);

const open = Boolean(anchorEl);

const handleClickListItem = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};

const handleMenuItemClick = (event: React.MouseEvent<HTMLElement>, option: TModuleSortOption) => {
setSort(option);
setAnchorEl(null);
};

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

return (
<>
<List component='nav' aria-label='Sort Modules' sx={{ p: "0" }}>
<button
style={{
lineHeight: "1",
textAlign: "center",
cursor: "pointer",
padding: "6px 12px",
height: "100%",
fontSize: "18px",
display: "flex",
alignItems: "center",
columnGap: "4px",
backgroundColor: "white",
border: "1px solid var(--custom-border-color-lighter)",
borderRadius: "5px",
color: "inherit",
}}
aria-controls={open ? "sort-menu" : undefined}
aria-haspopup='true'
aria-expanded={open ? "true" : undefined}
onClick={handleClickListItem}
id='sort-modules-button'
>
<MdSort fontSize='24px' />
<span style={{ fontWeight: "500" }}>Sort</span>
</button>
</List>
<Menu
id='sort-menu'
anchorEl={anchorEl}
open={open}
onClose={handleClose}
sx={{
"&& .Mui-selected": {
backgroundColor: "var(--custom-border-color-lighter)",
},
"&& .Mui-selected:hover": {
backgroundColor: "var(--custom-border-color-lighter)",
},
}}
MenuListProps={{
"aria-labelledby": "sort-modules-button",
role: "listbox",
}}
>
{moduleSortOptions.map((option) => (
<MenuItem key={option} selected={option === sort} onClick={(event) => handleMenuItemClick(event, option)}>
{option}
</MenuItem>
))}
</Menu>
</>
);
};
42 changes: 18 additions & 24 deletions src/components/Home/Modules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,24 @@ import { BiTrash } from "react-icons/bi";
//Functions
import { saveFile } from "../../utils/saveFile";
import { parseJSON } from "../../utils/parseJSON";
import { addExampleModuleToLocalStorage, isExampleModuleAdded } from "./helpers";
import {
addExampleModuleToLocalStorage,
getLocalStorageModules,
isExampleModuleAdded,
sortLocalStorageModules,
} from "./helpers";

//Interfaces and Types
import { IModule } from "../module/module";
import { TSettings } from "../../utils/types";
import { TModuleSortOption } from "./ModuleSortButton";

interface IModules {
sort: TModuleSortOption;
}
//Component
export const Modules = () => {
const { modules, loading } = useAllModules();
export const Modules: React.FC<IModules> = ({ sort }) => {
const { modules, loading } = useAllModules(sort);
const { handleExport, handleDelete, handlePopoverButtonClick, anchorEl, handlePopoverClose } = useHomePopover();

//Display loading spinner while component loads
Expand Down Expand Up @@ -75,7 +84,7 @@ export const Modules = () => {
};

// Return the whole localStorage
const useAllModules = () => {
const useAllModules = (sort: TModuleSortOption) => {
const [loading, setLoading] = useState(true);
const [modules, setModules] = useState<IModule[]>([]);

Expand All @@ -91,29 +100,14 @@ const useAllModules = () => {
}

//Setup variables for the module
let localStorageModules: IModule[] = [];

Object.entries(localStorage).forEach((key) => {
if (key[0].startsWith("repeatio-module")) {
//Get item, transform to object, on error add to moduleErrors array
try {
const module = localStorage.getItem(key[0]);
const moduleJSON = parseJSON<IModule>(module);
if (moduleJSON !== undefined && moduleJSON !== null) {
localStorageModules.push(moduleJSON);
}
} catch (error) {
if (error instanceof Error) {
toast.warn(`${key[0]}: ${error.message}`);
}
}
}
});
const localStorageModules: IModule[] = getLocalStorageModules();

const sortedLocalStorageModules = sortLocalStorageModules(localStorageModules, sort);

//Update states
setModules(localStorageModules);
setModules(sortedLocalStorageModules);
setLoading(false);
}, []);
}, [sort]);

//Refetch the modules if the localeStorage changes
const onStorageChange = useCallback(() => {
Expand Down
Loading

0 comments on commit a393f53

Please sign in to comment.