|
|
|
import { IconDefinition } from "@fortawesome/free-solid-svg-icons";
|
|
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
|
|
import { setSidebar } from "@redux/actions";
|
|
|
|
import { useReduxAction, useReduxStore } from "@redux/hooks/base";
|
|
|
|
import logo from "@static/logo64.png";
|
|
|
|
import React, {
|
|
|
|
createContext,
|
|
|
|
FunctionComponent,
|
|
|
|
useContext,
|
|
|
|
useMemo,
|
|
|
|
useState,
|
|
|
|
} from "react";
|
|
|
|
import {
|
|
|
|
Badge,
|
|
|
|
Collapse,
|
|
|
|
Container,
|
|
|
|
Image,
|
|
|
|
ListGroup,
|
|
|
|
ListGroupItem,
|
|
|
|
} from "react-bootstrap";
|
|
|
|
import { NavLink, useHistory, useRouteMatch } from "react-router-dom";
|
|
|
|
import { BuildKey } from "utilities";
|
|
|
|
import { useGotoHomepage } from "utilities/hooks";
|
|
|
|
import { useNavigationItems } from "../Navigation";
|
|
|
|
import { Navigation } from "../Navigation/nav";
|
|
|
|
import "./style.scss";
|
|
|
|
|
|
|
|
const SelectionContext = createContext<{
|
|
|
|
selection: string | null;
|
|
|
|
select: (selection: string | null) => void;
|
|
|
|
}>({ selection: null, select: () => {} });
|
|
|
|
|
|
|
|
const Sidebar: FunctionComponent = () => {
|
|
|
|
const open = useReduxStore((s) => s.showSidebar);
|
|
|
|
|
|
|
|
const changeSidebar = useReduxAction(setSidebar);
|
|
|
|
|
|
|
|
const cls = ["sidebar-container"];
|
|
|
|
const overlay = ["sidebar-overlay"];
|
|
|
|
|
|
|
|
if (open) {
|
|
|
|
cls.push("open");
|
|
|
|
overlay.push("open");
|
|
|
|
}
|
|
|
|
|
|
|
|
const goHome = useGotoHomepage();
|
|
|
|
|
|
|
|
const [selection, setSelection] = useState<string | null>(null);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<SelectionContext.Provider
|
|
|
|
value={{ selection: selection, select: setSelection }}
|
|
|
|
>
|
|
|
|
<aside className={cls.join(" ")}>
|
|
|
|
<Container className="sidebar-title d-flex align-items-center d-md-none">
|
|
|
|
<Image
|
|
|
|
alt="brand"
|
|
|
|
src={logo}
|
|
|
|
width="32"
|
|
|
|
height="32"
|
|
|
|
onClick={goHome}
|
|
|
|
className="cursor-pointer"
|
|
|
|
></Image>
|
|
|
|
</Container>
|
|
|
|
<SidebarNavigation></SidebarNavigation>
|
|
|
|
</aside>
|
|
|
|
<div
|
|
|
|
className={overlay.join(" ")}
|
|
|
|
onClick={() => changeSidebar(false)}
|
|
|
|
></div>
|
|
|
|
</SelectionContext.Provider>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const SidebarNavigation: FunctionComponent = () => {
|
|
|
|
const navItems = useNavigationItems();
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ListGroup variant="flush">
|
|
|
|
{navItems.map((v, idx) => {
|
|
|
|
if ("routes" in v) {
|
|
|
|
return (
|
|
|
|
<SidebarParent key={BuildKey(idx, v.name)} {...v}></SidebarParent>
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
<SidebarChild
|
|
|
|
parent=""
|
|
|
|
key={BuildKey(idx, v.name)}
|
|
|
|
{...v}
|
|
|
|
></SidebarChild>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
})}
|
|
|
|
</ListGroup>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const SidebarParent: FunctionComponent<Navigation.RouteWithChild> = ({
|
|
|
|
icon,
|
|
|
|
badge,
|
|
|
|
name,
|
|
|
|
path,
|
|
|
|
routes,
|
|
|
|
enabled,
|
|
|
|
component,
|
|
|
|
}) => {
|
|
|
|
const computedBadge = useMemo(() => {
|
|
|
|
let computed = badge ?? 0;
|
|
|
|
|
|
|
|
computed += routes.reduce((prev, curr) => {
|
|
|
|
return prev + (curr.badge ?? 0);
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
return computed !== 0 ? computed : undefined;
|
|
|
|
}, [badge, routes]);
|
|
|
|
|
|
|
|
const enabledRoutes = useMemo(
|
|
|
|
() => routes.filter((v) => v.enabled !== false && v.routeOnly !== true),
|
|
|
|
[routes]
|
|
|
|
);
|
|
|
|
|
|
|
|
const changeSidebar = useReduxAction(setSidebar);
|
|
|
|
|
|
|
|
const { selection, select } = useContext(SelectionContext);
|
|
|
|
|
|
|
|
const match = useRouteMatch({ path });
|
|
|
|
const open = match !== null || selection === path;
|
|
|
|
|
|
|
|
const collapseBoxClass = useMemo(
|
|
|
|
() => `sidebar-collapse-box ${open ? "active" : ""}`,
|
|
|
|
[open]
|
|
|
|
);
|
|
|
|
|
|
|
|
const history = useHistory();
|
|
|
|
|
|
|
|
if (enabled === false) {
|
|
|
|
return null;
|
|
|
|
} else if (enabledRoutes.length === 0) {
|
|
|
|
if (component) {
|
|
|
|
return (
|
|
|
|
<NavLink
|
|
|
|
activeClassName="sb-active"
|
|
|
|
className="list-group-item list-group-item-action sidebar-button"
|
|
|
|
to={path}
|
|
|
|
onClick={() => changeSidebar(false)}
|
|
|
|
>
|
|
|
|
<SidebarContent
|
|
|
|
icon={icon}
|
|
|
|
name={name}
|
|
|
|
badge={computedBadge}
|
|
|
|
></SidebarContent>
|
|
|
|
</NavLink>
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={collapseBoxClass}>
|
|
|
|
<ListGroupItem
|
|
|
|
action
|
|
|
|
className="sidebar-button"
|
|
|
|
onClick={() => {
|
|
|
|
if (open) {
|
|
|
|
select(null);
|
|
|
|
} else {
|
|
|
|
select(path);
|
|
|
|
}
|
|
|
|
if (component !== undefined) {
|
|
|
|
history.push(path);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<SidebarContent
|
|
|
|
icon={icon}
|
|
|
|
name={name}
|
|
|
|
badge={computedBadge}
|
|
|
|
></SidebarContent>
|
|
|
|
</ListGroupItem>
|
|
|
|
<Collapse in={open}>
|
|
|
|
<div className="sidebar-collapse">
|
|
|
|
{enabledRoutes.map((v, idx) => (
|
|
|
|
<SidebarChild
|
|
|
|
key={BuildKey(idx, v.name, "child")}
|
|
|
|
parent={path}
|
|
|
|
{...v}
|
|
|
|
></SidebarChild>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</Collapse>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
interface SidebarChildProps {
|
|
|
|
parent: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
const SidebarChild: FunctionComponent<
|
|
|
|
SidebarChildProps & Navigation.RouteWithoutChild
|
|
|
|
> = ({ icon, name, path, badge, enabled, routeOnly, parent }) => {
|
|
|
|
const changeSidebar = useReduxAction(setSidebar);
|
|
|
|
const { select } = useContext(SelectionContext);
|
|
|
|
|
|
|
|
if (enabled === false || routeOnly === true) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<NavLink
|
|
|
|
activeClassName="sb-active"
|
|
|
|
className="list-group-item list-group-item-action sidebar-button sb-collapse"
|
|
|
|
to={parent + path}
|
|
|
|
onClick={() => {
|
|
|
|
select(null);
|
|
|
|
changeSidebar(false);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<SidebarContent icon={icon} name={name} badge={badge}></SidebarContent>
|
|
|
|
</NavLink>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const SidebarContent: FunctionComponent<{
|
|
|
|
icon?: IconDefinition;
|
|
|
|
name: string;
|
|
|
|
badge?: number;
|
|
|
|
}> = ({ icon, name, badge }) => {
|
|
|
|
return (
|
|
|
|
<React.Fragment>
|
|
|
|
{icon && (
|
|
|
|
<FontAwesomeIcon
|
|
|
|
size="1x"
|
|
|
|
className="icon"
|
|
|
|
icon={icon}
|
|
|
|
></FontAwesomeIcon>
|
|
|
|
)}
|
|
|
|
<span className="d-flex flex-grow-1 justify-content-between">
|
|
|
|
{name} <Badge variant="secondary">{badge !== 0 ? badge : null}</Badge>
|
|
|
|
</span>
|
|
|
|
</React.Fragment>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default Sidebar;
|