mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
feature(Sidebar): BIG-374 filtering the sidebar items based on each item feature support.
This commit is contained in:
@@ -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'} />,
|
||||
|
||||
@@ -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
33
src/utils/deep.js
Normal 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,
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user