containers/Menu/index.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import SVGComponent from '../../components/SVG/SVGComponent';
import {
  getUser,
  getSelectedMenu,
  getSelectedMenuItem,
  toggleMenu,
  checkPermisson,
  hasFieldDataModified,
} from '../../selectors/common';
import { PAGES, MENU, SVG_ICONS, MENU_ITEMS, MENU_ROUTES, MENU_ICONS, PERMISSIONS, MODALS } from '../../constants';
import { aSetNavigationInfo, aSetOpenModal, aSetSelectedMenu } from '../../reducers/common';
import { aLogout } from '../../reducers/actions';
import { withRouterHooks } from '../../utils/router';
import { aLocationChange } from '../../reducers/bonus';

const mapToProps = (state) => ({
  user: getUser(state),
  selectedMenu: getSelectedMenu(state),
  selectedMenuItem: getSelectedMenuItem(state),
  toggleMenu: toggleMenu(state),
  checkPermisson: (payload) => checkPermisson(state, payload),
  hasFieldDataModified: hasFieldDataModified(state),
});

const actionsToProps = (dispatch) => ({
  logout: (payload) => dispatch(aLogout(payload)),
  setSelectedMenu: (payload) => dispatch(aSetSelectedMenu(payload)),
  setOpenModal: (payload) => dispatch(aSetOpenModal(payload)),
  setNavigationInfo: (payload) => dispatch(aSetNavigationInfo(payload)),
  changeLocation: (payload) => dispatch(aLocationChange(payload)),
});

/**
 * @class
 * @property {object} props
 * @property {object} props.user Logged user
 * @property {string} props.selectedMenu Selected root item page
 * @property {string} props.selectedMenuItem Selected item from dropdown
 * @property {boolean} props.toggleMenu Indicate is menu item is changed and
 * dropdowns need to recalculate
 * @property {boolean} props.checkPermisson Show only allowed menu items
 * @property {boolean} props.hasFieldDataModified check for modified field data
 *
 * @property {Function} props.logout Logout user
 * @property {Function} props.setSelectedMenu Change selected root item from sidebar
 * @property {Function} props.setOpenModal open modal
 * @property {Function} props.setNavigationInfo set link for navigation
 */
class Menu extends Component {
  constructor() {
    super();
    /**
     * @member {object}
     * @property {boolean} isOpened Indicate is dropdown opened
     */
    this.state = {
      isOpened: false,
    };
  }

  /**
   * Open dropdown on side nav
   *
   * @returns {void}
   */
  componentDidMount() {
    this.toggleMenu(this.props.selectedMenu, this.props.selectedMenu);
  }

  /**
   * Toggle menu on toggleMenu flag change
   *
   * @param {object} prevProps
   * @returns {void}
   */
  componentDidUpdate(prevProps) {
    if (this.props.toggleMenu !== prevProps.toggleMenu && this.props.selectedMenu !== prevProps.selectedMenu) {
      this.toggleMenu(this.props.selectedMenu, prevProps.selectedMenu);
    }
  }

  /**
   * Return class name for root items
   *
   * @function
   * @param {string} menuName
   * @returns {string}
   */
  getMenuClass = (menuName) => {
    let result = 'side-nav__list-item-link has-dropdown ';
    if (menuName === this.props.selectedMenu && this.state.isOpened) {
      result += 'active open ';
    }
    return result;
  };

  /**
   * Return class name for dropdown items
   *
   * @function
   * @param {string} menuItemName
   * @returns {string}
   */
  getMenuItemClass = (menuItemName) => {
    let result = 'side-nav__dropdown-link ';
    if (menuItemName === this.props.selectedMenuItem) {
      result += 'active ';
    }
    return result;
  };

  /**
   * Return only autificated routes
   *
   * @function
   * @param {string} route
   * @returns {Array}
   */
  getAuthItems = (route) => {
    const newItems = [];
    Object.keys(MENU_ITEMS[route]).map((key) => {
      if (this.props.checkPermisson(PERMISSIONS[key])) {
        newItems.push(MENU_ITEMS[route][key]);
      }
    });
    return newItems;
  };

  /**
   * Toggle dropdowns
   *
   * @function
   * @param {string} menuName
   * @param {string} prevMenu
   * @returns {void}
   */
  toggleMenu = (menuName, prevMenu) => {
    let isOpened = !this.state.isOpened;
    if (prevMenu !== menuName) {
      isOpened = true;
      this.props.setSelectedMenu({ menu: menuName });
      const prevElement = document.getElementById(`${prevMenu}-side-nav`);
      if (prevElement) {
        prevElement.style.maxHeight = '0px';
      }
    }
    const element = document.getElementById(`${menuName}-side-nav`);
    if (element) {
      if (isOpened) {
        element.style.maxHeight = `${element.scrollHeight}px`;

        setTimeout(() => {
          const selectedElement = document.getElementById(`item-${menuName}`);
          selectedElement.scrollIntoView({ block: 'start', behavior: 'smooth' });
        }, 400);
      } else {
        element.style.maxHeight = '0px';
      }
    }
    this.setState({
      isOpened,
    });
  };

  /**
   * Logout user
   *
   * @function
   * @returns {void}
   */
  logout = () => {
    this.props.logout({
      callAPI: true,
      navigate: this.props.navigate,
      location: this.props.location,
    });
  };

  /**
   * Open confirmation modal before navigating
   *
   * @function
   * @param {string} link
   * @returns {void}
   */
  handleNavigationClick = (link) => {
    if (this.props.hasFieldDataModified) {
      this.props.setOpenModal({
        modal: MODALS.SAVE_BEFORE_NAVIGATION,
      });
      this.props.setNavigationInfo({
        nextNavigationLink: link,
      });
    } else if (this.props.location.pathname !== link) {
      this.props.changeLocation({ pathname: link });
      this.props.navigate(link);
    }
  };

  /**
   * Render menu items
   *
   * @function
   * @returns {view}
   */
  renderMenu = () => (
    <div className="side-nav__list">
      {MENU_ROUTES.map((route) => {
        const children = this.getAuthItems(route);
        return (
          children.length > 0 && (
            <div id={`item-${MENU[route].key}`} className="side-nav__list-item" key={route}>
              <div
                className={this.getMenuClass(MENU[route].key)}
                onClick={this.toggleMenu.bind(this, MENU[route].key, this.props.selectedMenu)}
              >
                <SVGComponent className="icon-xs" src={`${SVG_ICONS.homeIcons}#${MENU_ICONS[route]}`} />
                <span>{MENU[route].name}</span>
              </div>
              <div className="side-nav__dropdown" id={`${MENU[route].key}-side-nav`}>
                {children.map((item) => (
                  <div
                    key={item.path}
                    className={this.getMenuItemClass(item.path)}
                    onClick={this.handleNavigationClick.bind(this, item.path)}
                  >
                    {item.name}
                  </div>
                ))}
              </div>
            </div>
          )
        );
      })}
    </div>
  );

  /**
   * Render home page
   *
   * @function
   * @returns {view}
   */
  renderHomePage = () => (
    <div className="home d-flex align-item-center">
      <div className="info-panel">
        <div className="s-logo" />
        <div className="info-panel__user">
          <div className="info-panel__user-name">{this.props.user && this.props.user.name}</div>
          <div
            id="myProfileBtn_homepage"
            className="side-nav__user-role"
            onClick={this.handleNavigationClick.bind(this, PAGES.MY_PROFILE)}
          >
            {MENU_ITEMS.PROFILE.MY_PROFILE.name}
          </div>
        </div>
        <div className="info-panel__button">
          <button id="logout_homepage" type="button" className="btn btn-primary" onClick={this.logout}>
            Logout
          </button>
        </div>
      </div>
      <div className="home-panel-wrapper">
        <div className="home-panel">
          <div className="home-panel__txt">
            Welcome to the Backoffice,
            <span>Choose your option below.</span>
          </div>
          <div className="side-nav side-nav--home">{this.renderMenu()}</div>
        </div>
        <div className="login__heading">Backoffice</div>
      </div>
    </div>
  );

  /**
   * Render side nav
   *
   * @function
   * @returns {view}
   */
  renderSideNav = () => (
    <div className="side-nav">
      <div className="s-logo xs" />
      <div className="side-nav__user">
        <div className="side-nav__user-name">{this.props.user && this.props.user.name}</div>
        <div className="side-nav__user-role" onClick={this.handleNavigationClick.bind(this, PAGES.MY_PROFILE)}>
          {MENU_ITEMS.PROFILE.MY_PROFILE.name}
        </div>
      </div>
      <div className="side-nav__button text-center">
        <button type="button" className="btn btn-primary" onClick={this.logout}>
          Logout
        </button>
      </div>
      {this.renderMenu()}
    </div>
  );

  /**
   * Render
   *
   * @returns {view}
   */
  render() {
    return this.props.location.pathname === PAGES.HOME ? this.renderHomePage() : this.renderSideNav();
  }
}

export default connect(mapToProps, actionsToProps)(withRouterHooks(Menu));