mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 05:10:31 +00:00
re-structure to monorepo.
This commit is contained in:
43
packages/webapp/src/containers/Dashboard/Sidebar/Sidebar.tsx
Normal file
43
packages/webapp/src/containers/Dashboard/Sidebar/Sidebar.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
|
||||
import { SidebarContainer } from './SidebarContainer';
|
||||
import { SidebarHead } from './SidebarHead';
|
||||
import { SidebarMenu } from './SidebarMenu';
|
||||
import { useMainSidebarMenu } from './hooks';
|
||||
import { SidebarOverlayBinded } from '../SidebarOverlay';
|
||||
|
||||
import '@/style/containers/Dashboard/Sidebar.scss';
|
||||
|
||||
/**
|
||||
* Dashboard sidebar.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function Sidebar() {
|
||||
const menu = useMainSidebarMenu();
|
||||
|
||||
return (
|
||||
<SidebarContainer>
|
||||
<SidebarHead />
|
||||
|
||||
<div className="sidebar__menu">
|
||||
<SidebarMenu menu={menu} />
|
||||
</div>
|
||||
<SidebarOverlayBinded />
|
||||
<SidebarFooterVersion />
|
||||
</SidebarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar footer version.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
function SidebarFooterVersion() {
|
||||
const { VERSION } = process.env;
|
||||
|
||||
if (!VERSION) {
|
||||
return null;
|
||||
}
|
||||
return <div class="sidebar__version">v{VERSION}</div>;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// @ts-nocheck
|
||||
import React, { useEffect } from 'react';
|
||||
import { Scrollbar } from 'react-scrollbars-custom';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import withDashboard from '@/containers/Dashboard/withDashboard';
|
||||
import withSubscriptions from '@/containers/Subscriptions/withSubscriptions';
|
||||
|
||||
import { useObserveSidebarExpendedBodyclass } from './hooks';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
* Sidebar container/
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function SidebarContainerJSX({
|
||||
// #ownProps
|
||||
children,
|
||||
|
||||
// #withDashboard
|
||||
sidebarExpended,
|
||||
|
||||
// #withSubscription
|
||||
isSubscriptionActive,
|
||||
}) {
|
||||
const sidebarScrollerRef = React.useRef();
|
||||
|
||||
// Toggles classname to body once sidebar expend/shrink.
|
||||
useObserveSidebarExpendedBodyclass(sidebarExpended);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sidebarExpended && sidebarScrollerRef.current) {
|
||||
sidebarScrollerRef.current.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
});
|
||||
}
|
||||
}, [sidebarExpended]);
|
||||
|
||||
const handleSidebarMouseLeave = () => {
|
||||
if (!sidebarExpended && sidebarScrollerRef.current) {
|
||||
sidebarScrollerRef.current.scrollTo({ top: 0, left: 0 });
|
||||
}
|
||||
};
|
||||
|
||||
const scrollerElementRef = React.useCallback((ref) => {
|
||||
sidebarScrollerRef.current = ref;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('sidebar', {
|
||||
'sidebar--mini-sidebar': !sidebarExpended,
|
||||
'is-subscription-inactive': !isSubscriptionActive,
|
||||
})}
|
||||
id="sidebar"
|
||||
onMouseLeave={handleSidebarMouseLeave}
|
||||
>
|
||||
<div className={'sidebar__scroll-wrapper'}>
|
||||
<Scrollbar
|
||||
noDefaultStyles={true}
|
||||
scrollerProps={{ elementRef: scrollerElementRef }}
|
||||
>
|
||||
<div className="sidebar__inner">{children}</div>
|
||||
</Scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const SidebarContainer = compose(
|
||||
withDashboard(({ sidebarExpended }) => ({
|
||||
sidebarExpended,
|
||||
})),
|
||||
withSubscriptions(
|
||||
({ isSubscriptionActive }) => ({ isSubscriptionActive }),
|
||||
'main',
|
||||
),
|
||||
)(SidebarContainerJSX);
|
||||
@@ -0,0 +1,69 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Button, Popover, Menu, Position } from '@blueprintjs/core';
|
||||
|
||||
import { Icon } from '@/components';
|
||||
|
||||
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
|
||||
import { useAuthenticatedAccount } from '@/hooks/query';
|
||||
import { compose, firstLettersArgs } from '@/utils';
|
||||
|
||||
// Popover modifiers.
|
||||
const POPOVER_MODIFIERS = {
|
||||
offset: { offset: '28, 8' },
|
||||
};
|
||||
|
||||
/**
|
||||
* Sideabr head.
|
||||
*/
|
||||
function SidebarHeadJSX({
|
||||
// #withCurrentOrganization
|
||||
organization,
|
||||
}) {
|
||||
// Retrieve authenticated user information.
|
||||
const { data: user } = useAuthenticatedAccount();
|
||||
|
||||
return (
|
||||
<div className="sidebar__head">
|
||||
<div className="sidebar__head-organization">
|
||||
<Popover
|
||||
modifiers={POPOVER_MODIFIERS}
|
||||
boundary={'window'}
|
||||
content={
|
||||
<Menu className={'menu--dashboard-organization'}>
|
||||
<div class="org-item">
|
||||
<div class="org-item__logo">
|
||||
{firstLettersArgs(...(organization.name || '').split(' '))}{' '}
|
||||
</div>
|
||||
<div class="org-item__name">{organization.name}</div>
|
||||
</div>
|
||||
</Menu>
|
||||
}
|
||||
position={Position.BOTTOM}
|
||||
minimal={true}
|
||||
>
|
||||
<Button
|
||||
className="title"
|
||||
rightIcon={<Icon icon={'caret-down-16'} size={16} />}
|
||||
>
|
||||
{organization.name}
|
||||
</Button>
|
||||
</Popover>
|
||||
<span class="subtitle">{user.full_name}</span>
|
||||
</div>
|
||||
|
||||
<div className="sidebar__head-logo">
|
||||
<Icon
|
||||
icon={'mini-bigcapital'}
|
||||
width={28}
|
||||
height={28}
|
||||
className="bigcapital--alt"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const SidebarHead = compose(
|
||||
withCurrentOrganization(({ organization }) => ({ organization })),
|
||||
)(SidebarHeadJSX);
|
||||
@@ -0,0 +1,75 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Menu } from '@blueprintjs/core';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { MenuItem, MenuItemLabel } from '@/components';
|
||||
import { ISidebarMenuItemType } from '@/containers/Dashboard/Sidebar/interfaces';
|
||||
import { useIsSidebarMenuItemActive } from './hooks';
|
||||
|
||||
import withSubscriptions from '@/containers/Subscriptions/withSubscriptions';
|
||||
|
||||
/**
|
||||
* Sidebar menu item.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function SidebarMenuItem({ item, index }) {
|
||||
// Detarmine whether the item is active.
|
||||
const isActive = useIsSidebarMenuItemActive(item);
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
text={item.text}
|
||||
disabled={item.disabled}
|
||||
dropdownType={item.dropdownType || 'collapse'}
|
||||
caretIconSize={16}
|
||||
onClick={item.onClick}
|
||||
active={isActive}
|
||||
hasSubmenu={item.hasChildren}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
SidebarMenuItem.ItemTypes = [
|
||||
ISidebarMenuItemType.Link,
|
||||
ISidebarMenuItemType.Overlay,
|
||||
ISidebarMenuItemType.Dialog,
|
||||
];
|
||||
|
||||
/**
|
||||
* Detarmines which sidebar menu item type should display.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function SidebarMenuItemComposer({ item, index }) {
|
||||
// Link item type.
|
||||
return SidebarMenuItem.ItemTypes.indexOf(item.type) !== -1 ? (
|
||||
<SidebarMenuItem item={item} index={index} />
|
||||
) : // Group item type.
|
||||
item.type === ISidebarMenuItemType.Group ? (
|
||||
<MenuItemLabel text={item.text} />
|
||||
) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar menu.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function SidebarMenuJSX({ menu }) {
|
||||
return (
|
||||
<div>
|
||||
<Menu className="sidebar-menu">
|
||||
{menu.map((item, index) => (
|
||||
<SidebarMenuItemComposer index={index} item={item} />
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const SidebarMenu = R.compose(
|
||||
withSubscriptions(
|
||||
({ isSubscriptionActive }) => ({ isSubscriptionActive }),
|
||||
'main',
|
||||
),
|
||||
)(SidebarMenuJSX);
|
||||
361
packages/webapp/src/containers/Dashboard/Sidebar/hooks.tsx
Normal file
361
packages/webapp/src/containers/Dashboard/Sidebar/hooks.tsx
Normal file
@@ -0,0 +1,361 @@
|
||||
// @ts-nocheck
|
||||
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 {
|
||||
useSidebarSubmnuActions,
|
||||
useDialogActions,
|
||||
useSubscription,
|
||||
useSidebarSubmenu,
|
||||
useFeatureCan,
|
||||
} from '@/hooks/state';
|
||||
import { SidebarMenu } from '@/constants/sidebarMenu';
|
||||
import {
|
||||
ISidebarMenuItemType,
|
||||
ISidebarSubscriptionAbility,
|
||||
} from './interfaces';
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicates whether the sidebar item has feature ability.
|
||||
*/
|
||||
function useFilterSidebarItemFeaturePredicater() {
|
||||
const { featureCan } = useFeatureCan();
|
||||
|
||||
return {
|
||||
// Returns false if the item has `feature` prop and that feature has no ability.
|
||||
predicate: (item) => {
|
||||
if (item.feature && !featureCan(item.feature)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicates whether the sidebar item has permissio ability.
|
||||
*/
|
||||
function useFilterSidebarItemAbilityPredicater() {
|
||||
const ability = useAbilityContext();
|
||||
|
||||
return {
|
||||
// Retruns false if the item has `permission` prop and that permission has no ability.
|
||||
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),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds click action for the sidebar overlay 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),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds click action of the given sidebar menu for each item based on item type.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// @ts-nocheck
|
||||
export enum ISidebarMenuItemType {
|
||||
Label = 'label',
|
||||
Link = 'link',
|
||||
Group = 'group',
|
||||
Overlay = 'overlay',
|
||||
Dialog = 'dialog',
|
||||
Drawer = 'drawer',
|
||||
}
|
||||
|
||||
export interface ISidebarMenuItemOverlay extends ISidebarMenuItemCommon {
|
||||
type: ISidebarMenuItemType.Overlay;
|
||||
}
|
||||
|
||||
export interface ISidebarMenuItemLink extends ISidebarMenuItemCommon {
|
||||
text: string | JSX.Element;
|
||||
href: string;
|
||||
type: ISidebarMenuItemType.Link;
|
||||
matchExact?: boolean;
|
||||
}
|
||||
|
||||
export interface ISidebarMenuItemDialog extends ISidebarMenuItemCommon {
|
||||
type: ISidebarMenuItemType.Dialog;
|
||||
dialogName: string;
|
||||
dialogPayload: any;
|
||||
}
|
||||
|
||||
export interface ISidebarMenuItemDrawer extends ISidebarMenuItemCommon {
|
||||
type: ISidebarMenuItemType.Drawer;
|
||||
drawerName: string;
|
||||
drawerPayload: any;
|
||||
}
|
||||
|
||||
export interface ISidebarMenuItemLabel extends ISidebarMenuItemCommon {
|
||||
text?: string;
|
||||
type: ISidebarMenuItemType.Label;
|
||||
}
|
||||
|
||||
export interface ISidebarMenuItemGroup extends ISidebarMenuItemCommon {
|
||||
type: ISidebarMenuItemType.Group;
|
||||
}
|
||||
|
||||
export interface ISidebarMenuItemPermission {
|
||||
subject: string;
|
||||
ability: string;
|
||||
}
|
||||
|
||||
export interface ISidebarMenuItemCommon {
|
||||
ability?: ISidebarMenuItemPermission | ISidebarMenuItemPermission[];
|
||||
feature?: string;
|
||||
disabled?: boolean;
|
||||
children?: ISidebarMenuItem[];
|
||||
onlySubscriptionExpired?: boolean;
|
||||
}
|
||||
|
||||
export type ISidebarMenuItem =
|
||||
| ISidebarMenuItemLink
|
||||
| ISidebarMenuItemLabel
|
||||
| ISidebarMenuItemGroup
|
||||
| ISidebarMenuItemOverlay
|
||||
| ISidebarMenuItemDialog
|
||||
| ISidebarMenuItemDrawer;
|
||||
|
||||
export enum ISidebarMenuOverlayIds {
|
||||
Items = 'Items',
|
||||
Reports = 'Reports',
|
||||
Sales = 'Sales',
|
||||
Purchases = 'Purchases',
|
||||
Financial = 'Financial',
|
||||
Contacts = 'Contacts',
|
||||
Cashflow = 'Cashflow',
|
||||
Expenses = 'Expenses',
|
||||
Projects = 'Projects',
|
||||
}
|
||||
|
||||
export enum ISidebarSubscriptionAbility {
|
||||
Expired = 'SubscriptionExpired',
|
||||
Active = 'SubscriptionActive',
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
export default (mapState) => {
|
||||
const mapStateToProps = (state, props) => {
|
||||
const mapped = {
|
||||
sidebarSubmenuOpen: state.dashboard.sidebarSubmenu.isOpen,
|
||||
sidebarSubmenuId: state.dashboard.sidebarSubmenu.submenuId,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// @ts-nocheck
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
closeSidebarSubmenu,
|
||||
openSidebarSubmenu,
|
||||
} from '@/store/dashboard/dashboard.actions';
|
||||
|
||||
const mapActionsToProps = (dispatch) => ({
|
||||
// Opens the dashboard submenu sidebar.
|
||||
openDashboardSidebarSubmenu: (submenuId) =>
|
||||
dispatch(openSidebarSubmenu(submenuId)),
|
||||
|
||||
// Closes the dashboard submenu sidebar.
|
||||
closeDashboardSidebarSubmenu: () => dispatch(closeSidebarSubmenu()),
|
||||
});
|
||||
|
||||
export default connect(null, mapActionsToProps);
|
||||
@@ -0,0 +1,142 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Overlay, OverlayProps } from '@blueprintjs/core';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { SidebarOverlayContainer } from './SidebarOverlayContainer';
|
||||
import { ISidebarMenuItem, ISidebarMenuItemType } from '../Sidebar/interfaces';
|
||||
|
||||
export interface ISidebarOverlayItem {
|
||||
text: string;
|
||||
href: string;
|
||||
divider?: boolean;
|
||||
label?: boolean;
|
||||
}
|
||||
|
||||
export interface ISidebarOverlayProps {
|
||||
items: ISidebarMenuItem[];
|
||||
}
|
||||
|
||||
export interface ISidebarOverlayItemProps {
|
||||
text: string | JSX.Element;
|
||||
href: string;
|
||||
onClick?: any;
|
||||
}
|
||||
|
||||
export interface ISidebarOverlayItemDivider {
|
||||
divider: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar overlay item.
|
||||
* @param {ISidebarOverlayItemProps}
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function SidebarOverlayItemLink({
|
||||
text,
|
||||
href,
|
||||
onClick,
|
||||
}: ISidebarOverlayItemProps) {
|
||||
return (
|
||||
<div className="sidebar-overlay__item">
|
||||
<Link to={href} onClick={onClick}>
|
||||
{text}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ISidebarOverlayItemLabel {
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar overlay label item.
|
||||
* @param {ISidebarOverlayItemLabel}
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function SidebarOverlayLabel({
|
||||
text,
|
||||
}: ISidebarOverlayItemLabel): JSX.Element {
|
||||
return <div className="sidebar-overlay__label">{text}</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar overlay divider item.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function SidebarOverlayDivider() {
|
||||
return <div className={'sidebar-overlay__divider'}></div>;
|
||||
}
|
||||
|
||||
interface SidebarOverlayItemProps {
|
||||
item: ISidebarMenuItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar overlay item.
|
||||
* @param {SidebarOverlayItemProps} props -
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function SidebarOverlayItem({ item }: SidebarOverlayItemProps) {
|
||||
//
|
||||
return item.type === ISidebarMenuItemType.Group ? (
|
||||
<SidebarOverlayLabel text={item.text} />
|
||||
) : //
|
||||
item.type === ISidebarMenuItemType.Link ||
|
||||
item.type === ISidebarMenuItemType.Dialog ? (
|
||||
<SidebarOverlayItemLink text={item.text} href={item.href} onClick={item.onClick} />
|
||||
) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface ISidebarOverlayMenu {
|
||||
items: ISidebarMenuItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar overlay menu.
|
||||
* @param {ISidebarOverlayMenu}
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function SidebarOverlayMenu({ items }: ISidebarOverlayMenu) {
|
||||
return (
|
||||
<div className="sidebar-overlay__menu">
|
||||
{items.map((item) => (
|
||||
<SidebarOverlayItem item={item} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface SidebarOverlayProps extends OverlayProps {
|
||||
items: ISidebarMenuItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar overlay component.
|
||||
* @param {SidebarOverlayProps}
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function SidebarOverlay({ items, label, ...rest }: SidebarOverlayProps) {
|
||||
return (
|
||||
<Overlay
|
||||
portalContainer={
|
||||
(document.querySelector('.Pane.vertical.Pane2') as HTMLElement) ||
|
||||
document.body
|
||||
}
|
||||
transitionDuration={100}
|
||||
backdropClassName={'sidebar-overlay-backdrop'}
|
||||
{...rest}
|
||||
>
|
||||
<div className="sidebar-overlay sidebar-overlay-transition">
|
||||
<SidebarOverlayContainer>
|
||||
{label && <SidebarOverlayLabel text={label} />}
|
||||
|
||||
<SidebarOverlayMenu items={items} />
|
||||
</SidebarOverlayContainer>
|
||||
</div>
|
||||
</Overlay>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { SidebarOverlay } from './SidebarOverlay';
|
||||
import withDashboardSidebarActions from '@/containers/Dashboard/Sidebar/withDashboardSidebarActions';
|
||||
import withDashboardSidebar from '@/containers/Dashboard/Sidebar/withDashboardSidebar';
|
||||
|
||||
import { useSubSidebarMenu } from '../Sidebar/hooks';
|
||||
|
||||
/**
|
||||
* Dashboard sidebar menu.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function SidebarOverlayBindedRoot({
|
||||
// #withDashboardSidebar
|
||||
sidebarSubmenuOpen,
|
||||
sidebarSubmenuId,
|
||||
|
||||
// #withDashboardSidebarActions
|
||||
closeDashboardSidebarSubmenu,
|
||||
}) {
|
||||
const handleSidebarClosing = React.useCallback(() => {
|
||||
closeDashboardSidebarSubmenu();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SidebarOverlayBindedRouter
|
||||
sidebarSubmenuId={sidebarSubmenuId}
|
||||
isOpen={sidebarSubmenuOpen}
|
||||
onClose={handleSidebarClosing}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard sidebar submenu router.
|
||||
*/
|
||||
function SidebarOverlayBindedRouter({ sidebarSubmenuId, ...rest }) {
|
||||
const sidebarItems = useSubSidebarMenu(sidebarSubmenuId);
|
||||
|
||||
return <SidebarOverlay items={sidebarItems} {...rest} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar overlay binded with redux.
|
||||
*/
|
||||
export const SidebarOverlayBinded = R.compose(
|
||||
withDashboardSidebar(({ sidebarSubmenuOpen, sidebarSubmenuId }) => ({
|
||||
sidebarSubmenuOpen,
|
||||
sidebarSubmenuId,
|
||||
})),
|
||||
withDashboardSidebarActions,
|
||||
)(SidebarOverlayBindedRoot);
|
||||
@@ -0,0 +1,22 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Scrollbar } from 'react-scrollbars-custom';
|
||||
|
||||
export interface ISidebarOverlayContainerProps {
|
||||
children: JSX.Element | JSX.Element[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar overlay container.
|
||||
*/
|
||||
export function SidebarOverlayContainer({
|
||||
children,
|
||||
}: ISidebarOverlayContainerProps) {
|
||||
return (
|
||||
<div className={'sidebar-overlay__scroll-wrapper'}>
|
||||
<Scrollbar noDefaultStyles={true}>
|
||||
<div className="sidebar-overlay__inner">{children}</div>
|
||||
</Scrollbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// @ts-nocheck
|
||||
export * from './SidebarOverlay';
|
||||
export * from './SidebarOverlayContainer';
|
||||
export * from './SidebarOverlayBinded'
|
||||
21
packages/webapp/src/containers/Dashboard/withDashboard.tsx
Normal file
21
packages/webapp/src/containers/Dashboard/withDashboard.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
export default (mapState) => {
|
||||
const mapStateToProps = (state, props) => {
|
||||
const mapped = {
|
||||
pageTitle: state.dashboard.pageTitle,
|
||||
pageSubtitle: state.dashboard.pageSubtitle,
|
||||
pageHint: state.dashboard.pageHint,
|
||||
editViewId: state.dashboard.topbarEditViewId,
|
||||
sidebarExpended: state.dashboard.sidebarExpended,
|
||||
preferencesPageTitle: state.dashboard.preferencesPageTitle,
|
||||
dashboardBackLink: state.dashboard.backLink,
|
||||
splashScreenLoading: state.dashboard.splashScreenLoading > 0,
|
||||
splashScreenCompleted: state.dashboard.splashScreenLoading === 0,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// @ts-nocheck
|
||||
import { connect } from 'react-redux';
|
||||
import t from '@/store/types';
|
||||
import {
|
||||
toggleExpendSidebar,
|
||||
} from '@/store/dashboard/dashboard.actions';
|
||||
import { splashStartLoading, splashStopLoading } from '@/store/dashboard/dashboard.actions';
|
||||
|
||||
const mapActionsToProps = (dispatch) => ({
|
||||
changePageTitle: (pageTitle) =>
|
||||
dispatch({
|
||||
type: t.CHANGE_DASHBOARD_PAGE_TITLE,
|
||||
pageTitle,
|
||||
}),
|
||||
|
||||
changePageSubtitle: (pageSubtitle) =>
|
||||
dispatch({
|
||||
type: t.ALTER_DASHBOARD_PAGE_SUBTITLE,
|
||||
pageSubtitle,
|
||||
}),
|
||||
|
||||
changePageHint: (pageHint) =>
|
||||
dispatch({
|
||||
type: t.CHANGE_DASHBOARD_PAGE_HINT,
|
||||
payload: { pageHint },
|
||||
}),
|
||||
|
||||
setTopbarEditView: (id) =>
|
||||
dispatch({
|
||||
type: t.SET_TOPBAR_EDIT_VIEW,
|
||||
id,
|
||||
}),
|
||||
|
||||
setDashboardRequestLoading: () =>
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
||||
}),
|
||||
|
||||
setDashboardRequestCompleted: () =>
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Toggles the sidebar expend.
|
||||
*/
|
||||
toggleSidebarExpand: (toggle) => dispatch(toggleExpendSidebar(toggle)),
|
||||
|
||||
changePreferencesPageTitle: (pageTitle) =>
|
||||
dispatch({
|
||||
type: 'CHANGE_PREFERENCES_PAGE_TITLE',
|
||||
pageTitle,
|
||||
}),
|
||||
|
||||
setDashboardBackLink: (backLink) =>
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_BACK_LINK,
|
||||
payload: { backLink },
|
||||
}),
|
||||
|
||||
// Splash screen start/stop loading.
|
||||
splashStartLoading: () => splashStartLoading(),
|
||||
splashStopLoading: () => splashStopLoading(),
|
||||
});
|
||||
|
||||
export default connect(null, mapActionsToProps);
|
||||
Reference in New Issue
Block a user