feature(Sidebar): BIG-374 filtering the sidebar items based on each item feature support.

This commit is contained in:
a.bouhuolia
2022-04-18 00:16:37 +02:00
parent 5e4e9c37c3
commit 8d1825a065
4 changed files with 198 additions and 59 deletions

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import { Features } from '../common/features';
import {
ISidebarMenuItemType,
ISidebarMenuOverlayIds,
@@ -74,6 +75,7 @@ export const SidebarMenu = [
text: <T id={'sidebar_warehouse_transfer'} />,
href: '/warehouses-transfers',
type: ISidebarMenuItemType.Link,
feature: Features.Warehouses
},
{
text: <T id={'category_list'} />,
@@ -105,6 +107,7 @@ export const SidebarMenu = [
),
href: '/warehouses-transfers/new',
type: ISidebarMenuItemType.Link,
feature: Features.Warehouses
},
{
text: <T id={'New service'} />,

View File

@@ -1,29 +1,31 @@
import _, { isEmpty } from 'lodash';
import React from 'react';
import deepdash from 'deepdash';
import * as R from 'ramda';
import { useHistory } from 'react-router-dom';
import { useAbilityContext } from 'hooks';
import { useSidebarSubmenu } from 'hooks/state';
import { useSidebarSubmenu, useFeatureCan } from 'hooks/state';
import { SidebarMenu } from 'config/sidebarMenu';
import { ISidebarMenuItemType } from './interfaces';
import { useSidebarSubmnuActions, useDialogActions } from 'hooks/state';
const deep = deepdash(_);
import { filterValuesDeep, deepdash } from 'utils';
const deepDashConfig = {
childrenPath: 'children',
pathFormat: 'array',
};
const ingoreTypesEmpty = [
ISidebarMenuItemType.Group,
ISidebarMenuItemType.Overlay,
];
/**
*
* @param {*} menu
* @returns
* Removes the all overlay items from the menu to the main-sidebar.
* @param {ISidebarMenuItem[]} menu
* @returns {ISidebarMenuItem[]}
*/
function removeSidebarOverlayChildren(menu) {
return deep.mapValuesDeep(
return deepdash.mapValuesDeep(
menu,
(item, key, parent, context) => {
if (item.type === ISidebarMenuItemType.Overlay) {
@@ -38,33 +40,54 @@ function removeSidebarOverlayChildren(menu) {
/**
* Retrives the main sidebar pre-menu.
* @returns
* @returns {ISidebarMenuItem[]}
*/
export function getMainSidebarMenu() {
return R.compose(removeSidebarOverlayChildren)(SidebarMenu);
}
/**
* Retrieves the sub sidebar pre-menu.
*/
export function getSubSidebarMenu() {}
function useFilterSidebarItemFeaturePredicater() {
const { featureCan } = useFeatureCan();
/**
*
* @param {*} menu
* @returns
*/
function useFilterSidebarMenuAbility(menu) {
return {
predicate: (item) => {
if (item.feature && !featureCan(item.feature)) {
return false;
}
return true;
},
};
}
function useFilterSidebarItemAbilityPredicater() {
const ability = useAbilityContext();
return deep.filterDeep(
return {
predicate: (item) => {
if (
item.permission &&
!ability.can(item.permission.ability, item.permission.subject)
) {
return false;
}
return true;
},
};
}
/**
* Filters sidebar menu items based on ability of the item permission.
* @param {*} menu
* @returns {}
*/
function useFilterSidebarMenuAbility(menu) {
const { predicate: predFeature } = useFilterSidebarItemFeaturePredicater();
const { predicate: predAbility } = useFilterSidebarItemAbilityPredicater();
return deepdash.filterDeep(
menu,
(item) => {
return (
(item.permission &&
ability.can(item.permission.ability, item.permission.subject)) ||
!item.permission
);
return predFeature(item) && predAbility(item);
},
deepDashConfig,
);
@@ -77,44 +100,103 @@ function useFilterSidebarMenuAbility(menu) {
*/
function useFlatSidebarMenu(menu) {
return React.useMemo(() => {
return deep.mapDeep(menu, (item) => item, deepDashConfig);
return deepdash.mapDeep(menu, (item) => item, deepDashConfig);
}, [menu]);
}
/**
*
* @param {*} menu
* @returns
* Binds sidebar link item click action.
* @param {ISidebarMenuItem} item
*/
function useBindSidebarItemLinkClick(menu) {
function useBindSidebarItemLinkClick() {
const history = useHistory();
const { toggleSidebarSubmenu, closeSidebarSubmenu } =
useSidebarSubmnuActions();
const { closeSidebarSubmenu } = useSidebarSubmnuActions();
// Handle sidebar item click.
const onClick = (item) => (event) => {
closeSidebarSubmenu();
history.push(item.href);
};
return {
bindOnClick: (item) => {
return {
...item,
onClick: onClick(item),
};
},
};
}
/**
* Bind sidebar dialog item click action.
* @param {ISidebarMenuItem} item
*/
function useBindSidebarItemDialogClick() {
const { closeSidebarSubmenu } = useSidebarSubmnuActions();
const { openDialog } = useDialogActions();
// Handle sidebar item click.
const handleClick = (item) => (event) => {
const onClick = (item) => (event) => {
closeSidebarSubmenu();
//
if (item.type === ISidebarMenuItemType.Overlay) {
toggleSidebarSubmenu({ submenuId: item.overlayId });
//
} else if (item.type === ISidebarMenuItemType.Link) {
history.push(item.href);
} else if (item.type === ISidebarMenuItemType.Dialog) {
openDialog(item.dialogName, item.dialogPayload);
}
openDialog(item.dialogName, item.dialogPayload);
};
return {
bindOnClick: (item) => {
return {
...item,
onClick: onClick(item),
};
},
};
}
function useBindSidebarItemOverlayClick() {
const { toggleSidebarSubmenu, closeSidebarSubmenu } =
useSidebarSubmnuActions();
// Handle sidebar item click.
const onClick = (item) => (event) => {
closeSidebarSubmenu();
toggleSidebarSubmenu({ submenuId: item.overlayId });
};
return {
bindOnClick: (item) => {
return {
...item,
onClick: onClick(item),
};
},
};
}
/**
*
* @param {} menu
* @returns {}
*/
function useBindSidebarItemClick(menu) {
const { bindOnClick: bindLinkClickEvt } = useBindSidebarItemLinkClick();
const { bindOnClick: bindOverlayClickEvt } = useBindSidebarItemOverlayClick();
const { bindOnClick: bindItemDialogEvt } = useBindSidebarItemDialogClick();
return React.useMemo(() => {
return deep.mapValuesDeep(
return deepdash.mapValuesDeep(
menu,
(item) => {
return {
...item,
onClick: handleClick(item),
};
return R.compose(
R.when(
R.propSatisfies(R.equals(ISidebarMenuItemType.Link), 'type'),
bindLinkClickEvt,
),
R.when(
R.propSatisfies(R.equals(ISidebarMenuItemType.Overlay), 'type'),
bindOverlayClickEvt,
),
R.when(
R.propSatisfies(R.equals(ISidebarMenuItemType.Dialog), 'type'),
bindItemDialogEvt,
),
)(item);
},
deepDashConfig,
);
@@ -122,10 +204,13 @@ function useBindSidebarItemLinkClick(menu) {
}
/**
*
* Finds the given overlay submenu id from the menu graph.
* @param {ISidebarMenuOverlayIds}
* @param {ISidebarMenuItem[]} menu -
* @returns {ISidebarMenuItem[]}
*/
const findSubmenuBySubmenuId = R.curry((submenuId, menu) => {
const groupItem = deep.findDeep(
const groupItem = deepdash.findDeep(
menu,
(item) => {
return (
@@ -140,25 +225,26 @@ const findSubmenuBySubmenuId = R.curry((submenuId, menu) => {
/**
* Retrieves the main sidebar post-menu.
* @returns {ISidebarMenuItem[]}
*/
export function useMainSidebarMenu() {
return R.compose(
useBindSidebarItemLinkClick,
useBindSidebarItemClick,
useFlatSidebarMenu,
removeSidebarOverlayChildren,
useAssocSidebarItemHasChildren,
filterSidebarItemHasNoChildren,
useFilterSidebarMenuAbility,
)(SidebarMenu);
}
/**
* Assoc `hasChildren` prop to sidebar menu items.
* @param {*} items
* @returns
* @param {ISidebarMenuItem[]} items
* @returns {ISidebarMenuItem[]}
*/
function useAssocSidebarItemHasChildren(items) {
return deep.mapValuesDeep(
return deepdash.mapValuesDeep(
items,
(item) => {
return {
@@ -172,15 +258,16 @@ function useAssocSidebarItemHasChildren(items) {
/**
* Retrieves the sub-sidebar post-menu.
* @param {*} submenuId
* @returns
* @param {ISidebarMenuOverlayIds} submenuId
* @returns {ISidebarMenuItem[]}
*/
export function useSubSidebarMenu(submenuId) {
if (!submenuId) return [];
return R.compose(
useBindSidebarItemLinkClick,
useBindSidebarItemClick,
useFlatSidebarMenu,
filterSidebarItemHasNoChildren,
useFilterSidebarMenuAbility,
findSubmenuBySubmenuId(submenuId),
)(SidebarMenu);
@@ -202,8 +289,22 @@ export function useObserveSidebarExpendedBodyclass(sidebarExpended) {
*/
export function useIsSidebarMenuItemActive(item) {
const { submenuId } = useSidebarSubmenu();
return (
item.type === ISidebarMenuItemType.Overlay && submenuId === item.overlayId
);
}
/**
* Filter sidebar specific items types that have no types.
* @param {ISidebarMenuItem[]} items -
* @returns {ISidebarMenuItem[]}
*/
export function filterSidebarItemHasNoChildren(items) {
return filterValuesDeep((item) => {
// If it was group item and has no children items so discard that item.
if (ingoreTypesEmpty.indexOf(item.type) !== -1 && isEmpty(item.children)) {
return false;
}
return true;
}, items);
}

33
src/utils/deep.js Normal file
View File

@@ -0,0 +1,33 @@
import _ from 'lodash';
import Deepdash from 'deepdash';
export const deepdash = Deepdash(_);
export const filterValuesDeep = (predicate, nodes) => {
return deepdash.condense(
deepdash.reduceDeep(
nodes,
(accumulator, value, key, parent, context) => {
const newValue = { ...value };
if (newValue.children) {
_.set(newValue, 'children', deepdash.condense(value.children));
}
const isTrue = predicate(newValue, key, parent, context);
if (isTrue === true) {
_.set(accumulator, context.path, newValue);
} else if (isTrue === false) {
_.unset(accumulator, context.path);
}
return accumulator;
},
[],
{
childrenPath: 'children',
pathFormat: 'array',
callbackAfterIterate: true,
},
),
);
};

View File

@@ -14,6 +14,8 @@ import { isEqual } from 'lodash';
import jsCookie from 'js-cookie';
export * from './deep';
export const getCookie = (name, defaultValue) =>
_.defaultTo(jsCookie.get(name), defaultValue);