Sidebar.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import React, { Component } from "react";
  2. import PropTypes from "prop-types";
  3. import { withTranslation, Trans } from "@/components/Common/Translate";
  4. import Link from "next/link";
  5. import Router, { withRouter } from "next/router";
  6. import { Collapse, Badge } from "reactstrap";
  7. import { connect } from "react-redux";
  8. import { bindActionCreators } from "redux";
  9. import * as actions from "../../store/actions/actions";
  10. import SidebarUserBlock from "./SidebarUserBlock";
  11. // import { getUser } from "@/actions/auth";
  12. import Menu from "./Menu.js";
  13. import MenuLLDIKTI from "./MenuLLDIKTI.js";
  14. import MenuPT from "./MenuPT.js";
  15. import MenuBypass from "./MenuBypass.js";
  16. import { ENV } from "../../env.js";
  17. import MenuReadOnly from "./MenuReadOnly.js";
  18. // localStorage.getItem("user");
  19. // import Menu from './MenuPT.js';
  20. // Helper to check for parrent of an given elements
  21. const parents = (element, selector) => {
  22. if (typeof selector !== "string") {
  23. return null;
  24. }
  25. const parents = [];
  26. let ancestor = element.parentNode;
  27. while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== 3 /*NODE_TEXT*/) {
  28. if (ancestor.matches(selector)) {
  29. parents.push(ancestor);
  30. }
  31. ancestor = ancestor.parentNode;
  32. }
  33. return parents;
  34. };
  35. // Helper to get outerHeight of a dom element
  36. const outerHeight = (elem, includeMargin) => {
  37. const style = getComputedStyle(elem);
  38. const margins = includeMargin ? parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10) : 0;
  39. return elem.offsetHeight + margins;
  40. };
  41. /**
  42. Component to display headings on sidebar
  43. */
  44. const SidebarItemHeader = ({ item }) => (
  45. <li className="nav-heading">
  46. <span>
  47. <Trans i18nKey={item.translate}>{item.heading}</Trans>
  48. </span>
  49. </li>
  50. );
  51. /**
  52. Normal items for the sidebar
  53. */
  54. const SidebarItem = ({ item, isActive, className, onMouseEnter }) => (
  55. <li className={isActive ? "active" : ""} onMouseEnter={onMouseEnter}>
  56. <Link href={item.path} as={item.as}>
  57. <a title={item.name}>
  58. {item.label && (
  59. <Badge tag="div" className="float-right" color={item.label.color}>
  60. {item.label.value}
  61. </Badge>
  62. )}
  63. {item.icon && <em className={item.icon} />}
  64. <span>
  65. <Trans i18nKey={item.translate}>{item.name}</Trans>
  66. </span>
  67. </a>
  68. </Link>
  69. </li>
  70. );
  71. /**
  72. Build a sub menu with items inside and attach collapse behavior
  73. */
  74. const SidebarSubItem = ({ item, isActive, handler, children, isOpen, onMouseEnter }) => (
  75. <li className={isActive ? "active" : ""}>
  76. <div className="nav-item" onClick={handler} onMouseEnter={onMouseEnter}>
  77. {item.label && (
  78. <Badge tag="div" className="float-right" color={item.label.color}>
  79. {item.label.value}
  80. </Badge>
  81. )}
  82. {item.icon && <em className={item.icon} />}
  83. <span>
  84. <Trans i18nKey={item.translate}>{item.name}</Trans>
  85. </span>
  86. </div>
  87. <Collapse isOpen={isOpen}>
  88. <ul id={item.path} className="sidebar-nav sidebar-subnav">
  89. {children}
  90. </ul>
  91. </Collapse>
  92. </li>
  93. );
  94. /**
  95. Component used to display a header on menu when using collapsed/hover mode
  96. */
  97. const SidebarSubHeader = ({ item }) => <li className="sidebar-subnav-header">{item.name}</li>;
  98. const SidebarBackdrop = ({ closeFloatingNav }) => <div className="sidebar-backdrop" onClick={closeFloatingNav} />;
  99. const FloatingNav = ({ item, target, routeActive, isFixed, closeFloatingNav }) => {
  100. let asideContainer = document.querySelector(".aside-container");
  101. let asideInner = asideContainer.firstElementChild; /*('.aside-inner')*/
  102. let sidebar = asideInner.firstElementChild; /*('.sidebar')*/
  103. let mar = parseInt(getComputedStyle(asideInner)["padding-top"], 0) + parseInt(getComputedStyle(asideContainer)["padding-top"], 0);
  104. let itemTop = target.parentElement.offsetTop + mar - sidebar.scrollTop;
  105. let vwHeight = document.body.clientHeight;
  106. const setPositionStyle = (el) => {
  107. if (!el) return;
  108. el.style.position = isFixed ? "fixed" : "absolute";
  109. el.style.top = itemTop + "px";
  110. el.style.bottom = outerHeight(el, true) + itemTop > vwHeight ? 0 : "auto";
  111. };
  112. return (
  113. <ul id={item.path} ref={setPositionStyle} className="sidebar-nav sidebar-subnav nav-floating" onMouseLeave={closeFloatingNav}>
  114. <SidebarSubHeader item={item} />
  115. {item.submenu.map((subitem, i) => (
  116. <SidebarItem key={i} item={subitem} isActive={routeActive(subitem.path)} />
  117. ))}
  118. </ul>
  119. );
  120. };
  121. /**
  122. The main sidebar component
  123. */
  124. class Sidebar extends Component {
  125. menu = [];
  126. state = {
  127. collapse: {},
  128. showSidebarBackdrop: false,
  129. currentFloatingItem: null,
  130. currentFloatingItemTarget: null,
  131. pathname: this.props.router.pathname,
  132. };
  133. async componentDidMount() {
  134. // const user = await getUser();
  135. const user = this.props.user;
  136. if (ENV === "production") {
  137. this.menu = user.role.id === 2022 ? MenuPT : user.user_id === "2A080F42-AE7F-407B-976E-DE5FA87BD277" || user.user_id === "28DB23AE-2976-47E0-9410-241A11EE1F88" ? MenuBypass : user.role.id === 2021 ? MenuLLDIKTI : user.role.id === 2024 ? MenuReadOnly : Menu;
  138. }
  139. if (ENV === "development") {
  140. this.menu = user.role.id === 2022 ? MenuPT : user.user_id === "28DB23AE-2976-47E0-9410-241A11EE1F88" ? MenuBypass : user.role.id === 2021 ? MenuLLDIKTI : user.role.id === 2024 ? MenuReadOnly : MenuPT;
  141. }
  142. if (ENV === "local") {
  143. this.menu = user.role.id === 2022 ? MenuPT : user.user_id === "28DB23AE-2976-47E0-9410-241A11EE1F88" ? MenuBypass : user.role.id === 2021 ? MenuLLDIKTI : user.role.id === 2024 ? MenuReadOnly : MenuPT;
  144. }
  145. // if (ENV === "development") {
  146. // this.menu = user.role.id === 2022 ? MenuPT : user.role.id === 2021 ? MenuLLDIKTI : user.role.id === 2024 ? MenuReadOnly : Menu;
  147. // }
  148. // this.menu = user.user_id === "2A080F42-AE7F-407B-976E-DE5FA87BD277" ? MenuBypass : user.role.id === 2022 ? MenuPT : user.role.id === 2021 ? MenuLLDIKTI : Menu;
  149. // prepare the flags to handle menu collapsed states
  150. this.buildCollapseList();
  151. // Listen for routes changes in order to hide the sidebar on mobile
  152. Router.events.on("routeChangeStart", this.handleRouteChange);
  153. Router.events.on("routeChangeComplete", this.handleRouteComplete);
  154. // Attach event listener to automatically close sidebar when click outside
  155. document.addEventListener("click", this.closeSidebarOnExternalClicks);
  156. }
  157. handleRouteComplete = (pathname) => {
  158. this.setState({
  159. pathname,
  160. });
  161. };
  162. handleRouteChange = () => {
  163. this.closeFloatingNav();
  164. this.closeSidebar();
  165. };
  166. componentWillUnmount() {
  167. document.removeEventListener("click", this.closeSidebarOnExternalClicks);
  168. Router.events.off("routeChangeStart", this.handleRouteChange);
  169. Router.events.off("routeChangeComplete", this.handleRouteComplete);
  170. }
  171. closeSidebar = () => {
  172. this.props.actions.toggleSetting("asideToggled");
  173. };
  174. closeSidebarOnExternalClicks = (e) => {
  175. // don't check if sidebar not visible
  176. if (!this.props.settings.asideToggled) return;
  177. if (
  178. !parents(e.target, ".aside-container").length && // if not child of sidebar
  179. !parents(e.target, ".topnavbar-wrapper").length && // if not child of header
  180. !e.target.matches("#user-block-toggle") && // user block toggle anchor
  181. !e.target.parentElement.matches("#user-block-toggle") // user block toggle icon
  182. ) {
  183. this.closeSidebar();
  184. }
  185. };
  186. /** prepare initial state of collapse menus.*/
  187. buildCollapseList = () => {
  188. let collapse = {};
  189. this.menu
  190. .filter(({ heading }) => !heading)
  191. .forEach(({ name, path, submenu }) => {
  192. collapse[name] = this.routeActive(submenu ? submenu.map(({ path }) => path) : path);
  193. });
  194. this.setState({ collapse });
  195. };
  196. routeActive = (paths) => {
  197. const currpath = this.state.pathname;
  198. paths = Array.isArray(paths) ? paths : [paths];
  199. return paths.some((p) => (p === "/" ? currpath === p : currpath.indexOf(p) > -1));
  200. };
  201. toggleItemCollapse = (stateName) => () => {
  202. for (let c in this.state.collapse) {
  203. if (this.state.collapse[c] === true && c !== stateName)
  204. this.setState({
  205. collapse: {
  206. [c]: false,
  207. },
  208. });
  209. }
  210. this.setState({
  211. collapse: {
  212. [stateName]: !this.state.collapse[stateName],
  213. },
  214. });
  215. };
  216. getSubRoutes = (item) => item.submenu.map(({ path }) => path);
  217. /** map menu config to string to determine which element to render */
  218. itemType = (item) => {
  219. if (item.heading) return "heading";
  220. if (!item.submenu) return "menu";
  221. if (item.submenu) return "submenu";
  222. };
  223. shouldUseFloatingNav = () => {
  224. return this.props.settings.isCollapsed || this.props.settings.isCollapsedText || this.props.settings.asideHover;
  225. };
  226. showFloatingNav = (item) => (e) => {
  227. if (this.shouldUseFloatingNav())
  228. this.setState({
  229. currentFloatingItem: item,
  230. currentFloatingItemTarget: e.currentTarget,
  231. showSidebarBackdrop: true,
  232. });
  233. };
  234. closeFloatingNav = () => {
  235. this.setState({
  236. currentFloatingItem: null,
  237. currentFloatingItemTarget: null,
  238. showSidebarBackdrop: false,
  239. });
  240. };
  241. render() {
  242. return (
  243. <>
  244. <aside className="aside-container">
  245. {/* START Sidebar (left) */}
  246. <div className="aside-inner">
  247. <nav className={"sidebar " + (this.props.settings.asideScrollbar ? "show-scrollbar" : "")}>
  248. {/* START sidebar nav */}
  249. <ul className="sidebar-nav">
  250. {/* START user info */}
  251. <li className="has-user-block">
  252. <SidebarUserBlock />
  253. </li>
  254. {/* END user info */}
  255. {/* Iterates over all sidebar items */}
  256. {this.menu.map((item, i) => {
  257. // heading
  258. if (this.itemType(item) === "heading") return <SidebarItemHeader item={item} key={i} />;
  259. else {
  260. if (this.itemType(item) === "menu") return <SidebarItem isActive={this.routeActive(item.path)} item={item} key={i} onMouseEnter={this.closeFloatingNav} />;
  261. if (this.itemType(item) === "submenu")
  262. return [
  263. <SidebarSubItem
  264. item={item}
  265. isOpen={this.state.collapse[item.name]}
  266. handler={this.toggleItemCollapse(item.name)}
  267. isActive={this.routeActive(this.getSubRoutes(item))}
  268. key={i}
  269. onMouseEnter={this.showFloatingNav(item)}
  270. >
  271. <SidebarSubHeader item={item} key={i} />
  272. {item.submenu.map((subitem, i) => (
  273. <SidebarItem key={i} item={subitem} isActive={this.routeActive(subitem.path)} />
  274. ))}
  275. </SidebarSubItem>,
  276. ];
  277. }
  278. return null; // unrecognized item
  279. })}
  280. </ul>
  281. {/* END sidebar nav */}
  282. </nav>
  283. </div>
  284. {/* END Sidebar (left) */}
  285. {this.state.currentFloatingItem && this.state.currentFloatingItem.submenu && (
  286. <FloatingNav item={this.state.currentFloatingItem} target={this.state.currentFloatingItemTarget} routeActive={this.routeActive} isFixed={this.props.settings.isFixed} closeFloatingNav={this.closeFloatingNav} />
  287. )}
  288. </aside>
  289. {this.state.showSidebarBackdrop && <SidebarBackdrop closeFloatingNav={this.closeFloatingNav} />}
  290. </>
  291. );
  292. }
  293. }
  294. Sidebar.propTypes = {
  295. actions: PropTypes.object,
  296. settings: PropTypes.object,
  297. };
  298. const mapStateToProps = (state) => ({ settings: state.settings, user: state.user });
  299. const mapDispatchToProps = (dispatch) => ({
  300. actions: bindActionCreators(actions, dispatch),
  301. });
  302. export default connect(mapStateToProps, mapDispatchToProps)(withRouter(withTranslation(Sidebar)));