Files
bigcapital/src/containers/Dashboard/Sidebar/hooks.js

350 lines
8.3 KiB
JavaScript

import _, { isEmpty, includes } from 'lodash';
import React from 'react';
import * as R from 'ramda';
import { useHistory } from 'react-router-dom';
import { useAbilityContext } from 'hooks';
import { useSidebarSubmenu, useFeatureCan } from 'hooks/state';
import { SidebarMenu } from 'config/sidebarMenu';
import {
ISidebarMenuItemType,
ISidebarSubscriptionAbility,
} from './interfaces';
import {
useSidebarSubmnuActions,
useDialogActions,
useSubscription,
} from 'hooks/state';
import { filterValuesDeep, deepdash } from 'utils';
const deepDashConfig = {
childrenPath: 'children',
pathFormat: 'array',
};
const ingoreTypesEmpty = [
ISidebarMenuItemType.Group,
ISidebarMenuItemType.Overlay,
];
/**
* Removes the all overlay items from the menu to the main-sidebar.
* @param {ISidebarMenuItem[]} menu
* @returns {ISidebarMenuItem[]}
*/
function removeSidebarOverlayChildren(menu) {
return deepdash.mapValuesDeep(
menu,
(item, key, parent, context) => {
if (item.type === ISidebarMenuItemType.Overlay) {
context.skipChildren(true);
return _.omit(item, ['children']);
}
return item;
},
deepDashConfig,
);
}
/**
* Retrives the main sidebar pre-menu.
* @returns {ISidebarMenuItem[]}
*/
export function getMainSidebarMenu() {
return R.compose(removeSidebarOverlayChildren)(SidebarMenu);
}
function useFilterSidebarItemFeaturePredicater() {
const { featureCan } = useFeatureCan();
return {
predicate: (item) => {
if (item.feature && !featureCan(item.feature)) {
return false;
}
return true;
},
};
}
function useFilterSidebarItemAbilityPredicater() {
const ability = useAbilityContext();
return {
predicate: (item) => {
if (
item.permission &&
!ability.can(item.permission.ability, item.permission.subject)
) {
return false;
}
return true;
},
};
}
/**
* Filters the sidebar item based on the subscription state.
*/
function useFilterSidebarItemSubscriptionPredicater() {
const { isSubscriptionActive, isSubscriptionInactive } = useSubscription();
return {
predicate: (item) => {
const { subscription } = item;
if (subscription) {
const isActive = includes(subscription, [
ISidebarSubscriptionAbility.Active,
])
? isSubscriptionActive
: true;
const isInactive = includes(subscription, [
ISidebarSubscriptionAbility.Inactive,
])
? isSubscriptionInactive
: true;
return isActive && isInactive;
}
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();
const { predicate: predSubscription } =
useFilterSidebarItemSubscriptionPredicater();
return deepdash.filterDeep(
menu,
(item) => {
return predFeature(item) && predAbility(item) && predSubscription(item);
},
deepDashConfig,
);
}
/**
* Flats the sidebar menu groups.
* @param {*} menu
* @returns {}
*/
function useFlatSidebarMenu(menu) {
return React.useMemo(() => {
return deepdash.mapDeep(menu, (item) => item, deepDashConfig);
}, [menu]);
}
/**
* Binds sidebar link item click action.
* @param {ISidebarMenuItem} item
*/
function useBindSidebarItemLinkClick() {
const history = useHistory();
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 onClick = (item) => (event) => {
closeSidebarSubmenu();
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 deepdash.mapValuesDeep(
menu,
(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,
);
}, [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 = deepdash.findDeep(
menu,
(item) => {
return (
item.type === ISidebarMenuItemType.Overlay &&
item.overlayId === submenuId
);
},
deepDashConfig,
);
return groupItem?.value?.children || [];
});
/**
* Retrieves the main sidebar post-menu.
* @returns {ISidebarMenuItem[]}
*/
export function useMainSidebarMenu() {
return R.compose(
useBindSidebarItemClick,
useFlatSidebarMenu,
removeSidebarOverlayChildren,
useAssocSidebarItemHasChildren,
filterSidebarItemHasNoChildren,
useFilterSidebarMenuAbility,
)(SidebarMenu);
}
/**
* Assoc `hasChildren` prop to sidebar menu items.
* @param {ISidebarMenuItem[]} items
* @returns {ISidebarMenuItem[]}
*/
function useAssocSidebarItemHasChildren(items) {
return deepdash.mapValuesDeep(
items,
(item) => {
return {
...item,
hasChildren: !isEmpty(item.children),
};
},
deepDashConfig,
);
}
/**
* Retrieves the sub-sidebar post-menu.
* @param {ISidebarMenuOverlayIds} submenuId
* @returns {ISidebarMenuItem[]}
*/
export function useSubSidebarMenu(submenuId) {
if (!submenuId) return [];
return R.compose(
useBindSidebarItemClick,
useFlatSidebarMenu,
filterSidebarItemHasNoChildren,
useFilterSidebarMenuAbility,
findSubmenuBySubmenuId(submenuId),
)(SidebarMenu);
}
/**
* Observes the sidebar expending with body class.
* @param {boolean} sidebarExpended
*/
export function useObserveSidebarExpendedBodyclass(sidebarExpended) {
React.useEffect(() => {
document.body.classList.toggle('has-mini-sidebar', !sidebarExpended);
}, [sidebarExpended]);
}
/**
* Detamrines whether the given sidebar menu item is active.
* @returns {boolean}
*/
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);
}