mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
re-structure to monorepo.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Icon } from '@/components';
|
||||
|
||||
import '@/style/components/BigcapitalLoading.scss';
|
||||
|
||||
/**
|
||||
* Bigcapital logo loading.
|
||||
*/
|
||||
export default function BigcapitalLoading({ className }) {
|
||||
return (
|
||||
<div className={classNames('bigcapital-loading', className)}>
|
||||
<div class="center">
|
||||
<Icon icon="bigcapital" height={37} width={228} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
63
packages/webapp/src/components/Dashboard/Dashboard.tsx
Normal file
63
packages/webapp/src/components/Dashboard/Dashboard.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router';
|
||||
|
||||
import '@/style/pages/Dashboard/Dashboard.scss';
|
||||
|
||||
import { Sidebar } from '@/containers/Dashboard/Sidebar/Sidebar';
|
||||
import DashboardContent from '@/components/Dashboard/DashboardContent';
|
||||
import DialogsContainer from '@/components/DialogsContainer';
|
||||
import PreferencesPage from '@/components/Preferences/PreferencesPage';
|
||||
import DashboardUniversalSearch from '@/containers/UniversalSearch/DashboardUniversalSearch';
|
||||
import DashboardSplitPane from '@/components/Dashboard/DashboardSplitePane';
|
||||
import GlobalHotkeys from './GlobalHotkeys';
|
||||
import DashboardProvider from './DashboardProvider';
|
||||
import DrawersContainer from '@/components/DrawersContainer';
|
||||
import AlertsContainer from '@/containers/AlertsContainer';
|
||||
import EnsureSubscriptionIsActive from '../Guards/EnsureSubscriptionIsActive';
|
||||
|
||||
/**
|
||||
* Dashboard preferences.
|
||||
*/
|
||||
function DashboardPreferences() {
|
||||
return (
|
||||
<EnsureSubscriptionIsActive>
|
||||
<DashboardSplitPane>
|
||||
<Sidebar />
|
||||
<PreferencesPage />
|
||||
</DashboardSplitPane>
|
||||
</EnsureSubscriptionIsActive>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard other routes.
|
||||
*/
|
||||
function DashboardAnyPage() {
|
||||
return (
|
||||
<DashboardSplitPane>
|
||||
<Sidebar />
|
||||
<DashboardContent />
|
||||
</DashboardSplitPane>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard page.
|
||||
*/
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<DashboardProvider>
|
||||
<Switch>
|
||||
<Route path="/preferences" component={DashboardPreferences} />
|
||||
<Route path="/" component={DashboardAnyPage} />
|
||||
</Switch>
|
||||
|
||||
<DashboardUniversalSearch />
|
||||
<DialogsContainer />
|
||||
<GlobalHotkeys />
|
||||
<DrawersContainer />
|
||||
<AlertsContainer />
|
||||
</DashboardProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Ability } from '@casl/ability';
|
||||
import { createContextualCan } from '@casl/react';
|
||||
|
||||
import { useDashboardMetaBoot } from './DashboardBoot';
|
||||
|
||||
export const AbilityContext = React.createContext();
|
||||
export const Can = createContextualCan(AbilityContext.Consumer);
|
||||
|
||||
/**
|
||||
* Dashboard ability provider.
|
||||
*/
|
||||
export function DashboardAbilityProvider({ children }) {
|
||||
const {
|
||||
meta: { abilities },
|
||||
} = useDashboardMetaBoot();
|
||||
|
||||
// Ability instance.
|
||||
const ability = new Ability(abilities);
|
||||
|
||||
return (
|
||||
<AbilityContext.Provider value={ability}>
|
||||
{children}
|
||||
</AbilityContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// @ts-nocheck
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
MenuItem,
|
||||
Menu,
|
||||
Popover,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
Divider,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
import { Icon } from '@/components';
|
||||
|
||||
/**
|
||||
* Dashboard action views list.
|
||||
*/
|
||||
export function DashboardActionViewsList({
|
||||
resourceName,
|
||||
allMenuItem,
|
||||
allMenuItemText,
|
||||
views,
|
||||
onChange,
|
||||
}) {
|
||||
const handleClickViewItem = (view) => {
|
||||
onChange && onChange(view);
|
||||
};
|
||||
|
||||
const viewsMenuItems = views.map((view) => (
|
||||
<MenuItem onClick={() => handleClickViewItem(view)} text={view.name} />
|
||||
));
|
||||
|
||||
const handleAllTabClick = () => {
|
||||
handleClickViewItem(null);
|
||||
};
|
||||
|
||||
const content = (
|
||||
<Menu>
|
||||
{allMenuItem && (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={handleAllTabClick}
|
||||
text={allMenuItemText || 'All'}
|
||||
/>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
{viewsMenuItems}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={content}
|
||||
minimal={true}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
>
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||
icon={<Icon icon="table-16" iconSize={16} />}
|
||||
text={<T id={'table_views'} />}
|
||||
rightIcon={'caret-down'}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
import { Navbar } from '@blueprintjs/core';
|
||||
|
||||
export function DashboardActionsBar({ className, children, name }) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
{
|
||||
'dashboard__actions-bar': true,
|
||||
[`dashboard__actions-bar--${name}`]: !!name,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Navbar className="navbar--dashboard-actions-bar">{children}</Navbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import withBreadcrumbs from 'react-router-breadcrumbs-hoc';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { getDashboardRoutes } from '@/routes/dashboard';
|
||||
import { If, Icon } from '@/components';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
import withDashboard from '@/containers/Dashboard/withDashboard';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
function DashboardBackLink({ dashboardBackLink, breadcrumbs }) {
|
||||
const history = useHistory();
|
||||
const crumb = breadcrumbs[breadcrumbs.length - 2];
|
||||
|
||||
const handleClick = (event) => {
|
||||
const url =
|
||||
typeof dashboardBackLink === 'string'
|
||||
? dashboardBackLink
|
||||
: crumb.match.url;
|
||||
history.push(url);
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
return (
|
||||
<If condition={dashboardBackLink && crumb}>
|
||||
<div class="dashboard__back-link">
|
||||
<a href="#no-link" onClick={handleClick}>
|
||||
<Icon icon={'arrow-left'} iconSize={18} /> <T id={'back_to_list'} />
|
||||
</a>
|
||||
</div>
|
||||
</If>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withBreadcrumbs([]),
|
||||
withDashboard(({ dashboardBackLink }) => ({
|
||||
dashboardBackLink,
|
||||
})),
|
||||
)(DashboardBackLink);
|
||||
129
packages/webapp/src/components/Dashboard/DashboardBoot.tsx
Normal file
129
packages/webapp/src/components/Dashboard/DashboardBoot.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import {
|
||||
useAuthenticatedAccount,
|
||||
useCurrentOrganization,
|
||||
useDashboardMeta,
|
||||
} from '@/hooks/query';
|
||||
import { useSplashLoading } from '@/hooks/state';
|
||||
import { useWatch, useWatchImmediate, useWhen } from '@/hooks';
|
||||
import { useSubscription } from '@/hooks/state';
|
||||
import { setCookie, getCookie } from '@/utils';
|
||||
|
||||
/**
|
||||
* Dashboard meta async booting.
|
||||
* - Fetches the dashboard meta only if the organization subscribe is active.
|
||||
* - Once the dashboard meta query is loading display dashboard splash screen.
|
||||
*/
|
||||
export function useDashboardMetaBoot() {
|
||||
const { isSubscriptionActive } = useSubscription();
|
||||
|
||||
const {
|
||||
data: dashboardMeta,
|
||||
isLoading: isDashboardMetaLoading,
|
||||
isSuccess: isDashboardMetaSuccess,
|
||||
} = useDashboardMeta({
|
||||
keepPreviousData: true,
|
||||
|
||||
// Avoid run the query if the organization subscription is not active.
|
||||
enabled: isSubscriptionActive,
|
||||
});
|
||||
const [startLoading, stopLoading] = useSplashLoading();
|
||||
|
||||
useWatchImmediate((value) => {
|
||||
value && startLoading();
|
||||
}, isDashboardMetaLoading);
|
||||
|
||||
useWatchImmediate(() => {
|
||||
isDashboardMetaSuccess && stopLoading();
|
||||
}, isDashboardMetaSuccess);
|
||||
|
||||
return {
|
||||
meta: dashboardMeta,
|
||||
isLoading: isDashboardMetaLoading,
|
||||
isSuccess: isDashboardMetaSuccess,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Application async booting.
|
||||
*/
|
||||
export function useApplicationBoot() {
|
||||
// Fetches the current user's organization.
|
||||
const {
|
||||
isSuccess: isCurrentOrganizationSuccess,
|
||||
isLoading: isOrgLoading,
|
||||
data: organization,
|
||||
} = useCurrentOrganization();
|
||||
|
||||
// Authenticated user.
|
||||
const { isSuccess: isAuthUserSuccess, isLoading: isAuthUserLoading } =
|
||||
useAuthenticatedAccount();
|
||||
|
||||
// Initial locale cookie value.
|
||||
const localeCookie = getCookie('locale');
|
||||
|
||||
// Is the dashboard booted.
|
||||
const isBooted = React.useRef(false);
|
||||
|
||||
// Syns the organization language with locale cookie.
|
||||
React.useEffect(() => {
|
||||
if (organization?.metadata?.language) {
|
||||
setCookie('locale', organization.metadata.language);
|
||||
}
|
||||
}, [organization]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Can't continue if the organization metadata is not loaded yet.
|
||||
if (!organization?.metadata?.language) {
|
||||
return;
|
||||
}
|
||||
// Can't continue if the organization is already booted.
|
||||
if (isBooted.current) {
|
||||
return;
|
||||
}
|
||||
// Reboot the application in case the initial locale not equal
|
||||
// the current organization language.
|
||||
if (localeCookie !== organization.metadata.language) {
|
||||
window.location.reload();
|
||||
}
|
||||
}, [localeCookie, organization]);
|
||||
|
||||
const [startLoading, stopLoading] = useSplashLoading();
|
||||
|
||||
// Splash loading when organization request loading and
|
||||
// applicaiton still not booted.
|
||||
useWatchImmediate((value) => {
|
||||
value && !isBooted.current && startLoading();
|
||||
}, isOrgLoading);
|
||||
|
||||
// Splash loading when request authenticated user loading and
|
||||
// application still not booted yet.
|
||||
useWatchImmediate((value) => {
|
||||
value && !isBooted.current && startLoading();
|
||||
}, isAuthUserLoading);
|
||||
|
||||
// Stop splash loading once organization request success.
|
||||
useWatch((value) => {
|
||||
value && stopLoading();
|
||||
}, isCurrentOrganizationSuccess);
|
||||
|
||||
// Stop splash loading once authenticated user request success.
|
||||
useWatch((value) => {
|
||||
value && stopLoading();
|
||||
}, isAuthUserSuccess);
|
||||
|
||||
// Once the all requests complete change the app loading state.
|
||||
useWhen(
|
||||
isAuthUserSuccess &&
|
||||
isCurrentOrganizationSuccess &&
|
||||
localeCookie === organization?.metadata?.language,
|
||||
() => {
|
||||
isBooted.current = true;
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
isLoading: isOrgLoading || isAuthUserLoading,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import {
|
||||
CollapsibleList,
|
||||
MenuItem,
|
||||
Classes,
|
||||
Boundary,
|
||||
} from '@blueprintjs/core';
|
||||
import withBreadcrumbs from 'react-router-breadcrumbs-hoc';
|
||||
import { getDashboardRoutes } from '@/routes/dashboard';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
function DashboardBreadcrumbs({ breadcrumbs }){
|
||||
const history = useHistory();
|
||||
|
||||
return(
|
||||
<CollapsibleList
|
||||
className={Classes.BREADCRUMBS}
|
||||
dropdownTarget={<span className={Classes.BREADCRUMBS_COLLAPSED} />}
|
||||
collapseFrom={Boundary.START}
|
||||
visibleItemCount={0}>
|
||||
{
|
||||
breadcrumbs.map(({ breadcrumb,match })=>{
|
||||
return (<MenuItem
|
||||
key={match.url}
|
||||
icon={'folder-close'}
|
||||
text={breadcrumb}
|
||||
onClick={() => history.push(match.url) } />)
|
||||
})
|
||||
}
|
||||
</CollapsibleList>
|
||||
)
|
||||
}
|
||||
|
||||
export default withBreadcrumbs([])(DashboardBreadcrumbs)
|
||||
18
packages/webapp/src/components/Dashboard/DashboardCard.tsx
Normal file
18
packages/webapp/src/components/Dashboard/DashboardCard.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
|
||||
// Dashboard card.
|
||||
export function DashboardCard({ children, page }) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(CLASSES.DASHBOARD_CARD, {
|
||||
[CLASSES.DASHBOARD_CARD_PAGE]: page,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import DashboardTopbar from '@/components/Dashboard/DashboardTopbar';
|
||||
import DashboardContentRoutes from '@/components/Dashboard/DashboardContentRoute';
|
||||
import DashboardErrorBoundary from './DashboardErrorBoundary';
|
||||
|
||||
export default React.forwardRef(({}, ref) => {
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={DashboardErrorBoundary}>
|
||||
<div className="dashboard-content" id="dashboard" ref={ref}>
|
||||
<DashboardTopbar />
|
||||
<DashboardContentRoutes />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { getDashboardRoutes } from '@/routes/dashboard';
|
||||
import EnsureSubscriptionsIsActive from '../Guards/EnsureSubscriptionsIsActive';
|
||||
import EnsureSubscriptionsIsInactive from '../Guards/EnsureSubscriptionsIsInactive';
|
||||
import DashboardPage from './DashboardPage';
|
||||
|
||||
/**
|
||||
* Dashboard inner route content.
|
||||
*/
|
||||
function DashboardContentRouteContent({ route }) {
|
||||
const content = (
|
||||
<DashboardPage
|
||||
name={route.name}
|
||||
Component={route.component}
|
||||
pageTitle={route.pageTitle}
|
||||
backLink={route.backLink}
|
||||
hint={route.hint}
|
||||
sidebarExpand={route.sidebarExpand}
|
||||
pageType={route.pageType}
|
||||
defaultSearchResource={route.defaultSearchResource}
|
||||
/>
|
||||
);
|
||||
return route.subscriptionActive ? (
|
||||
<EnsureSubscriptionsIsInactive
|
||||
subscriptionTypes={route.subscriptionActive}
|
||||
children={content}
|
||||
redirectTo={'/billing'}
|
||||
/>
|
||||
) : route.subscriptionInactive ? (
|
||||
<EnsureSubscriptionsIsActive
|
||||
subscriptionTypes={route.subscriptionInactive}
|
||||
children={content}
|
||||
redirectTo={'/'}
|
||||
/>
|
||||
) : (
|
||||
content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard content route.
|
||||
*/
|
||||
export default function DashboardContentRoute() {
|
||||
const routes = getDashboardRoutes();
|
||||
|
||||
return (
|
||||
<Route pathname="/">
|
||||
<Switch>
|
||||
{routes.map((route, index) => (
|
||||
<Route exact={route.exact} key={index} path={`${route.path}`}>
|
||||
<DashboardContentRouteContent route={route} />
|
||||
</Route>
|
||||
))}
|
||||
</Switch>
|
||||
</Route>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
|
||||
/**
|
||||
* Dashboard content table.
|
||||
*/
|
||||
export function DashboardContentTable({ children }) {
|
||||
return (
|
||||
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>{children}</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { FormattedMessage as T, Icon } from '@/components';
|
||||
|
||||
export default function DashboardErrorBoundary({}) {
|
||||
return (
|
||||
<div class="dashboard__error-boundary">
|
||||
<h1><T id={'sorry_about_that_something_went_wrong'} /></h1>
|
||||
<p><T id={'if_the_problem_stuck_please_contact_us_as_soon_as_possible'} /></p>
|
||||
<Icon icon="bigcapital" height={30} width={160} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import intl from "react-intl-universal";
|
||||
import { Classes, Button } from '@blueprintjs/core';
|
||||
import { T, Icon } from '@/components';
|
||||
|
||||
/**
|
||||
* Dashboard advanced filter button.
|
||||
*/
|
||||
export function DashboardFilterButton({ conditionsCount }) {
|
||||
return (
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL, 'button--filter', {
|
||||
'has-active-filters': conditionsCount > 0,
|
||||
})}
|
||||
text={
|
||||
conditionsCount > 0 ? (
|
||||
intl.get('count_filters_applied', { count: conditionsCount })
|
||||
) : (
|
||||
<T id={'filter'} />
|
||||
)
|
||||
}
|
||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
26
packages/webapp/src/components/Dashboard/DashboardFooter.tsx
Normal file
26
packages/webapp/src/components/Dashboard/DashboardFooter.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { getFooterLinks } from '@/constants/footerLinks';
|
||||
import { For } from '@/components';
|
||||
|
||||
function FooterLinkItem({ title, link }) {
|
||||
return (
|
||||
<div class="">
|
||||
<a href={link} target="_blank" rel="noopener noreferrer">
|
||||
{title}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DashboardFooter() {
|
||||
const footerLinks = getFooterLinks();
|
||||
|
||||
return (
|
||||
<div class="dashboard__footer">
|
||||
<div class="footer-links">
|
||||
<For render={FooterLinkItem} of={footerLinks} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { LoadingIndicator } from '../Indicator';
|
||||
|
||||
export function DashboardInsider({
|
||||
loading,
|
||||
children,
|
||||
name,
|
||||
mount = false,
|
||||
className,
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
{
|
||||
dashboard__insider: true,
|
||||
'dashboard__insider--loading': loading,
|
||||
[`dashboard__insider--${name}`]: !!name,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<LoadingIndicator loading={loading} mount={mount}>
|
||||
{children}
|
||||
</LoadingIndicator>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Choose } from '@/components';
|
||||
import BigcapitalLoading from './BigcapitalLoading';
|
||||
|
||||
/**
|
||||
* Dashboard loading indicator.
|
||||
*/
|
||||
export default function DashboardLoadingIndicator({
|
||||
isLoading = false,
|
||||
className,
|
||||
children,
|
||||
}) {
|
||||
return (
|
||||
<Choose>
|
||||
<Choose.When condition={isLoading}>
|
||||
<BigcapitalLoading />
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
{ children }
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
);
|
||||
}
|
||||
105
packages/webapp/src/components/Dashboard/DashboardPage.tsx
Normal file
105
packages/webapp/src/components/Dashboard/DashboardPage.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
// @ts-nocheck
|
||||
import React, { useEffect, Suspense } from 'react';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
|
||||
import { compose } from '@/utils';
|
||||
import { Spinner } from '@blueprintjs/core';
|
||||
|
||||
import withUniversalSearchActions from '@/containers/UniversalSearch/withUniversalSearchActions';
|
||||
|
||||
/**
|
||||
* Dashboard pages wrapper.
|
||||
*/
|
||||
function DashboardPage({
|
||||
// #ownProps
|
||||
pageTitle,
|
||||
backLink,
|
||||
sidebarExpand = true,
|
||||
Component,
|
||||
name,
|
||||
hint,
|
||||
defaultSearchResource,
|
||||
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
setDashboardBackLink,
|
||||
changePageHint,
|
||||
toggleSidebarExpand,
|
||||
|
||||
// #withUniversalSearch
|
||||
setResourceTypeUniversalSearch,
|
||||
resetResourceTypeUniversalSearch,
|
||||
}) {
|
||||
// Hydrate the given page title.
|
||||
useEffect(() => {
|
||||
pageTitle && changePageTitle(pageTitle);
|
||||
|
||||
return () => {
|
||||
pageTitle && changePageTitle('');
|
||||
};
|
||||
});
|
||||
|
||||
// Hydrate the given page hint.
|
||||
useEffect(() => {
|
||||
hint && changePageHint(hint);
|
||||
|
||||
return () => {
|
||||
hint && changePageHint('');
|
||||
};
|
||||
}, [hint, changePageHint]);
|
||||
|
||||
// Hydrate the dashboard back link status.
|
||||
useEffect(() => {
|
||||
backLink && setDashboardBackLink(backLink);
|
||||
|
||||
return () => {
|
||||
backLink && setDashboardBackLink(false);
|
||||
};
|
||||
}, [backLink, setDashboardBackLink]);
|
||||
|
||||
useEffect(() => {
|
||||
const className = `page-${name}`;
|
||||
name && document.body.classList.add(className);
|
||||
|
||||
return () => {
|
||||
name && document.body.classList.remove(className);
|
||||
};
|
||||
}, [name]);
|
||||
|
||||
useEffect(() => {
|
||||
toggleSidebarExpand(sidebarExpand);
|
||||
}, [toggleSidebarExpand, sidebarExpand]);
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultSearchResource) {
|
||||
setResourceTypeUniversalSearch(defaultSearchResource);
|
||||
}
|
||||
return () => {
|
||||
resetResourceTypeUniversalSearch();
|
||||
};
|
||||
}, [
|
||||
defaultSearchResource,
|
||||
resetResourceTypeUniversalSearch,
|
||||
setResourceTypeUniversalSearch,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={CLASSES.DASHBOARD_PAGE}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<div class="dashboard__fallback-loading">
|
||||
<Spinner size={40} value={null} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Component />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
// withUniversalSearch,
|
||||
withUniversalSearchActions,
|
||||
)(DashboardPage);
|
||||
@@ -0,0 +1,9 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Dashboard page content.
|
||||
*/
|
||||
export function DashboardPageContent({ children }) {
|
||||
return <div class="dashboard__page-content">{children}</div>;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { DashboardAbilityProvider } from '../../components';
|
||||
import { useDashboardMetaBoot } from './DashboardBoot';
|
||||
|
||||
/**
|
||||
* Dashboard provider.
|
||||
*/
|
||||
export default function DashboardProvider({ children }) {
|
||||
const { isLoading } = useDashboardMetaBoot();
|
||||
|
||||
// Avoid display any dashboard component before complete booting.
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
return <DashboardAbilityProvider>{children}</DashboardAbilityProvider>;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
PopoverInteractionKind,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Classes,
|
||||
Tooltip,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import clsx from 'classnames';
|
||||
import { Icon, T } from '@/components';
|
||||
|
||||
import Style from './style.module.scss';
|
||||
|
||||
/**
|
||||
* Dashboard rows height button control.
|
||||
*/
|
||||
export function DashboardRowsHeightButton({ initialValue, value, onChange }) {
|
||||
const [localSize, setLocalSize] = React.useState(initialValue);
|
||||
|
||||
// Handle menu item click.
|
||||
const handleItemClick = (size) => (event) => {
|
||||
setLocalSize(size);
|
||||
onChange && onChange(size, event);
|
||||
};
|
||||
// Button icon name.
|
||||
const btnIcon = `table-row-${localSize}`;
|
||||
|
||||
return (
|
||||
<Popover
|
||||
minimal={true}
|
||||
content={
|
||||
<Menu className={Style.menu}>
|
||||
<MenuDivider title={<T id={'dashboard.rows_height'} />} />
|
||||
<MenuItem
|
||||
onClick={handleItemClick('small')}
|
||||
text={<T id={'dashboard.row_small'} />}
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={handleItemClick('medium')}
|
||||
text={<T id={'dashboard.row_medium'} />}
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
placement="bottom-start"
|
||||
modifiers={{
|
||||
offset: { offset: '0, 4' },
|
||||
}}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
>
|
||||
<Tooltip
|
||||
content={<T id={'dashboard.rows_height'} />}
|
||||
minimal={true}
|
||||
position={Position.BOTTOM}
|
||||
>
|
||||
<Button
|
||||
className={clsx(Classes.MINIMAL, Style.button)}
|
||||
icon={<Icon icon={btnIcon} iconSize={16} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
DashboardRowsHeightButton.defaultProps = {
|
||||
initialValue: 'medium',
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
.menu{
|
||||
:global .bp3-heading{
|
||||
font-weight: 400;
|
||||
opacity: 0.5;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.button{
|
||||
min-width: 34px;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// @ts-nocheck
|
||||
import React, { useState, useRef } from 'react';
|
||||
import SplitPane from 'react-split-pane';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import withDashboard from '@/containers/Dashboard/withDashboard';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
function DashboardSplitPane({
|
||||
sidebarExpended,
|
||||
children
|
||||
}) {
|
||||
const initialSize = 220;
|
||||
|
||||
const [defaultSize, setDefaultSize] = useState(
|
||||
parseInt(localStorage.getItem('dashboard-size'), 10) || initialSize,
|
||||
);
|
||||
const debounceSaveSize = useRef(
|
||||
debounce((size) => {
|
||||
localStorage.setItem('dashboard-size', size);
|
||||
}, 500),
|
||||
);
|
||||
const handleChange = (size) => {
|
||||
debounceSaveSize.current(size);
|
||||
setDefaultSize(size);
|
||||
}
|
||||
return (
|
||||
<SplitPane
|
||||
allowResize={sidebarExpended}
|
||||
split="vertical"
|
||||
minSize={180}
|
||||
maxSize={300}
|
||||
defaultSize={sidebarExpended ? defaultSize : 50}
|
||||
size={sidebarExpended ? defaultSize : 50}
|
||||
onChange={handleChange}
|
||||
className="primary"
|
||||
>
|
||||
{children}
|
||||
</SplitPane>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboard(({ sidebarExpended }) => ({ sidebarExpended }))
|
||||
)(DashboardSplitPane);
|
||||
@@ -0,0 +1,23 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { ThemeProvider, StyleSheetManager } from 'styled-components';
|
||||
import rtlcss from 'stylis-rtlcss';
|
||||
import { useAppIntlContext } from '../AppIntlProvider';
|
||||
|
||||
interface DashboardThemeProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function DashboardThemeProvider({
|
||||
children,
|
||||
}: DashboardThemeProviderProps) {
|
||||
const { direction } = useAppIntlContext();
|
||||
|
||||
return (
|
||||
<StyleSheetManager
|
||||
{...(direction === 'rtl' ? { stylisPlugins: [rtlcss] } : {})}
|
||||
>
|
||||
<ThemeProvider theme={{ dir: direction }}>{children}</ThemeProvider>
|
||||
</StyleSheetManager>
|
||||
);
|
||||
}
|
||||
216
packages/webapp/src/components/Dashboard/DashboardTopbar.tsx
Normal file
216
packages/webapp/src/components/Dashboard/DashboardTopbar.tsx
Normal file
@@ -0,0 +1,216 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import {
|
||||
Navbar,
|
||||
NavbarGroup,
|
||||
NavbarDivider,
|
||||
Button,
|
||||
Classes,
|
||||
Tooltip,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
|
||||
import DashboardTopbarUser from '@/components/Dashboard/TopbarUser';
|
||||
import DashboardBreadcrumbs from '@/components/Dashboard/DashboardBreadcrumbs';
|
||||
import DashboardBackLink from '@/components/Dashboard/DashboardBackLink';
|
||||
import { Icon, Hint, If } from '@/components';
|
||||
|
||||
import withUniversalSearchActions from '@/containers/UniversalSearch/withUniversalSearchActions';
|
||||
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
|
||||
import withDashboard from '@/containers/Dashboard/withDashboard';
|
||||
|
||||
import QuickNewDropdown from '@/containers/QuickNewDropdown/QuickNewDropdown';
|
||||
import { compose } from '@/utils';
|
||||
import withSubscriptions from '@/containers/Subscriptions/withSubscriptions';
|
||||
import { useGetUniversalSearchTypeOptions } from '@/containers/UniversalSearch/utils';
|
||||
|
||||
function DashboardTopbarSubscriptionMessage() {
|
||||
return (
|
||||
<div class="dashboard__topbar-subscription-msg">
|
||||
<span>
|
||||
<T id={'dashboard.subscription_msg.period_over'} />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DashboardHamburgerButton({ ...props }) {
|
||||
return (
|
||||
<Button minimal={true} {...props}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
role="img"
|
||||
focusable="false"
|
||||
>
|
||||
<title>
|
||||
<T id={'menu'} />
|
||||
</title>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-miterlimit="5"
|
||||
stroke-width="2"
|
||||
d="M4 7h15M4 12h15M4 17h15"
|
||||
></path>
|
||||
</svg>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard topbar.
|
||||
*/
|
||||
function DashboardTopbar({
|
||||
// #withDashboard
|
||||
pageTitle,
|
||||
editViewId,
|
||||
pageHint,
|
||||
|
||||
// #withDashboardActions
|
||||
toggleSidebarExpand,
|
||||
|
||||
// #withDashboard
|
||||
sidebarExpended,
|
||||
|
||||
// #withGlobalSearch
|
||||
openGlobalSearch,
|
||||
|
||||
// #withSubscriptions
|
||||
isSubscriptionActive,
|
||||
isSubscriptionInactive,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
const handlerClickEditView = () => {
|
||||
history.push(`/custom_views/${editViewId}/edit`);
|
||||
};
|
||||
|
||||
const handleSidebarToggleBtn = () => {
|
||||
toggleSidebarExpand();
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="dashboard__topbar">
|
||||
<div class="dashboard__topbar-left">
|
||||
<div class="dashboard__topbar-sidebar-toggle">
|
||||
<Tooltip
|
||||
content={
|
||||
!sidebarExpended ? (
|
||||
<T id={'open_sidebar'} />
|
||||
) : (
|
||||
<T id={'close_sidebar'} />
|
||||
)
|
||||
}
|
||||
position={Position.RIGHT}
|
||||
>
|
||||
<DashboardHamburgerButton onClick={handleSidebarToggleBtn} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="dashboard__title">
|
||||
<h1>{pageTitle}</h1>
|
||||
|
||||
<If condition={pageHint}>
|
||||
<div class="dashboard__hint">
|
||||
<Hint content={pageHint} />
|
||||
</div>
|
||||
</If>
|
||||
|
||||
<If condition={editViewId}>
|
||||
<Button
|
||||
className={Classes.MINIMAL + ' button--view-edit'}
|
||||
icon={<Icon icon="pen" iconSize={13} />}
|
||||
onClick={handlerClickEditView}
|
||||
/>
|
||||
</If>
|
||||
</div>
|
||||
|
||||
<div class="dashboard__breadcrumbs">
|
||||
<DashboardBreadcrumbs />
|
||||
</div>
|
||||
<DashboardBackLink />
|
||||
</div>
|
||||
|
||||
<div class="dashboard__topbar-right">
|
||||
<If condition={isSubscriptionInactive}>
|
||||
<DashboardTopbarSubscriptionMessage />
|
||||
</If>
|
||||
|
||||
<Navbar class="dashboard__topbar-navbar">
|
||||
<NavbarGroup>
|
||||
<If condition={isSubscriptionActive}>
|
||||
<DashboardQuickSearchButton
|
||||
onClick={() => openGlobalSearch(true)}
|
||||
/>
|
||||
<QuickNewDropdown />
|
||||
|
||||
<Tooltip
|
||||
content={<T id={'notifications'} />}
|
||||
position={Position.BOTTOM}
|
||||
>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'notification-24'} iconSize={20} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</If>
|
||||
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'help-24'} iconSize={20} />}
|
||||
text={<T id={'help'} />}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
</NavbarGroup>
|
||||
</Navbar>
|
||||
|
||||
<div class="dashboard__topbar-user">
|
||||
<DashboardTopbarUser />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withUniversalSearchActions,
|
||||
withDashboard(({ pageTitle, pageHint, editViewId, sidebarExpended }) => ({
|
||||
pageTitle,
|
||||
editViewId,
|
||||
sidebarExpended,
|
||||
pageHint,
|
||||
})),
|
||||
withDashboardActions,
|
||||
withSubscriptions(
|
||||
({ isSubscriptionActive, isSubscriptionInactive }) => ({
|
||||
isSubscriptionActive,
|
||||
isSubscriptionInactive,
|
||||
}),
|
||||
'main',
|
||||
),
|
||||
)(DashboardTopbar);
|
||||
|
||||
/**
|
||||
* Dashboard quick search button.
|
||||
*/
|
||||
function DashboardQuickSearchButton({ ...rest }) {
|
||||
const searchTypeOptions = useGetUniversalSearchTypeOptions();
|
||||
|
||||
// Can't continue if there is no any search type option.
|
||||
if (searchTypeOptions.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'search-24'} iconSize={20} />}
|
||||
text={<T id={'quick_find'} />}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
104
packages/webapp/src/components/Dashboard/DashboardViewsTabs.tsx
Normal file
104
packages/webapp/src/components/Dashboard/DashboardViewsTabs.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
// @ts-nocheck
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Tabs, Tab, Tooltip, Position } from '@blueprintjs/core';
|
||||
import { useHistory } from 'react-router';
|
||||
import { debounce } from 'lodash';
|
||||
import { If, Icon } from '@/components';
|
||||
import { saveInvoke } from '@/utils';
|
||||
|
||||
/**
|
||||
* Dashboard views tabs.
|
||||
*/
|
||||
export function DashboardViewsTabs({
|
||||
initialViewSlug = 0,
|
||||
currentViewSlug,
|
||||
tabs,
|
||||
defaultTabText = <T id={'all'} />,
|
||||
allTab = true,
|
||||
newViewTab = true,
|
||||
resourceName,
|
||||
onNewViewTabClick,
|
||||
onChange,
|
||||
OnThrottledChange,
|
||||
throttleTime = 250,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const [currentView, setCurrentView] = useState(initialViewSlug || 0);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
typeof currentViewSlug !== 'undefined' &&
|
||||
currentViewSlug !== currentView
|
||||
) {
|
||||
setCurrentView(currentViewSlug || 0);
|
||||
}
|
||||
}, [currentView, setCurrentView, currentViewSlug]);
|
||||
|
||||
const throttledOnChange = useRef(
|
||||
debounce((viewId) => saveInvoke(OnThrottledChange, viewId), throttleTime),
|
||||
);
|
||||
|
||||
// Trigger `onChange` and `onThrottledChange` events.
|
||||
const triggerOnChange = (viewSlug) => {
|
||||
const value = viewSlug === 0 ? null : viewSlug;
|
||||
saveInvoke(onChange, value);
|
||||
throttledOnChange.current(value);
|
||||
};
|
||||
|
||||
// Handles click a new view.
|
||||
const handleClickNewView = () => {
|
||||
history.push(`/custom_views/${resourceName}/new`);
|
||||
onNewViewTabClick && onNewViewTabClick();
|
||||
};
|
||||
|
||||
// Handle tabs change.
|
||||
const handleTabsChange = (viewSlug) => {
|
||||
setCurrentView(viewSlug);
|
||||
triggerOnChange(viewSlug);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="dashboard__views-tabs">
|
||||
<Tabs
|
||||
id="navbar"
|
||||
large={true}
|
||||
selectedTabId={currentView}
|
||||
className="tabs--dashboard-views"
|
||||
onChange={handleTabsChange}
|
||||
animate={false}
|
||||
>
|
||||
{allTab && <Tab id={0} title={defaultTabText} />}
|
||||
|
||||
{tabs.map((tab) => (
|
||||
<Tab id={tab.slug} title={tab.name} />
|
||||
))}
|
||||
<If condition={newViewTab}>
|
||||
<Tooltip
|
||||
content={<T id={'create_a_new_view'} />}
|
||||
position={Position.RIGHT}
|
||||
>
|
||||
<Button
|
||||
className="button--new-view"
|
||||
icon={<Icon icon="plus" />}
|
||||
onClick={handleClickNewView}
|
||||
minimal={true}
|
||||
/>
|
||||
</Tooltip>
|
||||
</If>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
DashboardViewsTabs.propTypes = {
|
||||
tabs: PropTypes.array.isRequired,
|
||||
allTab: PropTypes.bool,
|
||||
newViewTab: PropTypes.bool,
|
||||
|
||||
onNewViewTabClick: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
OnThrottledChange: PropTypes.func,
|
||||
throttleTime: PropTypes.number,
|
||||
};
|
||||
46
packages/webapp/src/components/Dashboard/GlobalHotkeys.tsx
Normal file
46
packages/webapp/src/components/Dashboard/GlobalHotkeys.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { getDashboardRoutes } from '@/routes/dashboard';
|
||||
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
|
||||
function GlobalHotkeys({
|
||||
// #withDashboardActions
|
||||
toggleSidebarExpend,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const routes = getDashboardRoutes();
|
||||
|
||||
const globalHotkeys = routes
|
||||
.filter(({ hotkey }) => hotkey)
|
||||
.map(({ hotkey }) => hotkey)
|
||||
.toString();
|
||||
|
||||
const handleSidebarToggleBtn = () => {
|
||||
toggleSidebarExpend();
|
||||
};
|
||||
useHotkeys(
|
||||
globalHotkeys,
|
||||
(event, handle) => {
|
||||
routes.map(({ path, hotkey }) => {
|
||||
if (handle.key === hotkey) {
|
||||
history.push(path);
|
||||
}
|
||||
});
|
||||
},
|
||||
[history],
|
||||
);
|
||||
useHotkeys('ctrl+/', (event, handle) => handleSidebarToggleBtn());
|
||||
useHotkeys('shift+d', (event, handle) => openDialog('money-in', {}));
|
||||
useHotkeys('shift+q', (event, handle) => openDialog('money-out', {}));
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
export default compose(withDashboardActions, withDialogActions)(GlobalHotkeys);
|
||||
35
packages/webapp/src/components/Dashboard/PrivatePages.tsx
Normal file
35
packages/webapp/src/components/Dashboard/PrivatePages.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router';
|
||||
|
||||
import Dashboard from '@/components/Dashboard/Dashboard';
|
||||
import SetupWizardPage from '@/containers/Setup/WizardSetupPage';
|
||||
|
||||
import EnsureOrganizationIsReady from '../Guards/EnsureOrganizationIsReady';
|
||||
import EnsureOrganizationIsNotReady from '../Guards/EnsureOrganizationIsNotReady';
|
||||
import { PrivatePagesProvider } from './PrivatePagesProvider';
|
||||
|
||||
import '@/style/pages/Dashboard/Dashboard.scss';
|
||||
|
||||
/**
|
||||
* Dashboard inner private pages.
|
||||
*/
|
||||
export default function DashboardPrivatePages() {
|
||||
return (
|
||||
<PrivatePagesProvider>
|
||||
<Switch>
|
||||
<Route path={'/setup'}>
|
||||
<EnsureOrganizationIsNotReady>
|
||||
<SetupWizardPage />
|
||||
</EnsureOrganizationIsNotReady>
|
||||
</Route>
|
||||
|
||||
<Route path="/">
|
||||
<EnsureOrganizationIsReady>
|
||||
<Dashboard />
|
||||
</EnsureOrganizationIsReady>
|
||||
</Route>
|
||||
</Switch>
|
||||
</PrivatePagesProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { useApplicationBoot } from '@/components';
|
||||
|
||||
/**
|
||||
* Private pages provider.
|
||||
*/
|
||||
export function PrivatePagesProvider({
|
||||
// #ownProps
|
||||
children,
|
||||
}) {
|
||||
const { isLoading } = useApplicationBoot();
|
||||
|
||||
return <React.Fragment>{!isLoading ? children : null}</React.Fragment>;
|
||||
}
|
||||
15
packages/webapp/src/components/Dashboard/SplashScreen.tsx
Normal file
15
packages/webapp/src/components/Dashboard/SplashScreen.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
import BigcapitalLoading from './BigcapitalLoading';
|
||||
import withDashboard from '@/containers/Dashboard/withDashboard';
|
||||
|
||||
function SplashScreenComponent({ splashScreenLoading }) {
|
||||
return splashScreenLoading ? <BigcapitalLoading /> : null;
|
||||
}
|
||||
|
||||
export const SplashScreen = R.compose(
|
||||
withDashboard(({ splashScreenLoading }) => ({
|
||||
splashScreenLoading,
|
||||
})),
|
||||
)(SplashScreenComponent);
|
||||
94
packages/webapp/src/components/Dashboard/TopbarUser.tsx
Normal file
94
packages/webapp/src/components/Dashboard/TopbarUser.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Button,
|
||||
Popover,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { If, FormattedMessage as T } from '@/components';
|
||||
|
||||
import { firstLettersArgs } from '@/utils';
|
||||
import { useAuthActions } from '@/hooks/state';
|
||||
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import withSubscriptions from '@/containers/Subscriptions/withSubscriptions';
|
||||
|
||||
import { useAuthenticatedAccount } from '@/hooks/query';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
* Dashboard topbar user.
|
||||
*/
|
||||
function DashboardTopbarUser({
|
||||
openDialog,
|
||||
|
||||
// #withSubscriptions
|
||||
isSubscriptionActive,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { setLogout } = useAuthActions();
|
||||
|
||||
// Retrieve authenticated user information.
|
||||
const { data: user } = useAuthenticatedAccount();
|
||||
|
||||
const onClickLogout = () => {
|
||||
setLogout();
|
||||
};
|
||||
|
||||
const onKeyboardShortcut = () => {
|
||||
openDialog('keyboard-shortcuts');
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
<Menu className={'menu--logged-user-dropdown'}>
|
||||
<MenuItem
|
||||
multiline={true}
|
||||
className={'menu-item--profile'}
|
||||
text={
|
||||
<div>
|
||||
<div class="person">
|
||||
{user.first_name} {user.last_name}
|
||||
</div>
|
||||
<div class="org">
|
||||
<T id="organization_id" />: {user.tenant_id}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<If condition={isSubscriptionActive}>
|
||||
<MenuItem
|
||||
text={<T id={'keyboard_shortcuts'} />}
|
||||
onClick={onKeyboardShortcut}
|
||||
/>
|
||||
<MenuItem
|
||||
text={<T id={'preferences'} />}
|
||||
onClick={() => history.push('/preferences')}
|
||||
/>
|
||||
</If>
|
||||
<MenuItem text={<T id={'logout'} />} onClick={onClickLogout} />
|
||||
</Menu>
|
||||
}
|
||||
position={Position.BOTTOM}
|
||||
>
|
||||
<Button>
|
||||
<div className="user-text">
|
||||
{firstLettersArgs(user.first_name, user.last_name)}
|
||||
</div>
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
withSubscriptions(
|
||||
({ isSubscriptionActive }) => ({ isSubscriptionActive }),
|
||||
'main',
|
||||
),
|
||||
)(DashboardTopbarUser);
|
||||
14
packages/webapp/src/components/Dashboard/index.tsx
Normal file
14
packages/webapp/src/components/Dashboard/index.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
// @ts-nocheck
|
||||
export * from './SplashScreen';
|
||||
export * from './DashboardBoot';
|
||||
export * from './DashboardThemeProvider';
|
||||
export * from './DashboardAbilityProvider';
|
||||
export * from './DashboardCard'
|
||||
export * from './DashboardActionsBar'
|
||||
export * from './DashboardFilterButton'
|
||||
export * from './DashboardRowsHeightButton'
|
||||
export * from './DashboardViewsTabs'
|
||||
export * from './DashboardActionViewsList'
|
||||
export * from './DashboardContentTable'
|
||||
export * from './DashboardPageContent'
|
||||
export * from './DashboardInsider'
|
||||
Reference in New Issue
Block a user