mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-06-01 15:39:00 +00:00
wip
This commit is contained in:
@@ -5,6 +5,7 @@ import { Switch, Route } from 'react-router';
|
||||
import '@/style/pages/Dashboard/Dashboard.scss';
|
||||
|
||||
import { Sidebar } from '@/containers/Dashboard/Sidebar/Sidebar';
|
||||
import { WorkspacesSidebar } from '@/containers/Dashboard/WorkspacesSidebar/WorkspacesSidebar';
|
||||
import DashboardContent from '@/components/Dashboard/DashboardContent';
|
||||
import DialogsContainer from '@/components/DialogsContainer';
|
||||
import PreferencesPage from '@/components/Preferences/PreferencesPage';
|
||||
@@ -21,10 +22,13 @@ import { DashboardSockets } from './DashboardSockets';
|
||||
*/
|
||||
function DashboardPreferences() {
|
||||
return (
|
||||
<DashboardSplitPane>
|
||||
<Sidebar />
|
||||
<PreferencesPage />
|
||||
</DashboardSplitPane>
|
||||
<div className="dashboard-layout">
|
||||
<WorkspacesSidebar />
|
||||
<DashboardSplitPane>
|
||||
<Sidebar />
|
||||
<PreferencesPage />
|
||||
</DashboardSplitPane>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,10 +37,13 @@ function DashboardPreferences() {
|
||||
*/
|
||||
function DashboardAnyPage() {
|
||||
return (
|
||||
<DashboardSplitPane>
|
||||
<Sidebar />
|
||||
<DashboardContent />
|
||||
</DashboardSplitPane>
|
||||
<div className="dashboard-layout">
|
||||
<WorkspacesSidebar />
|
||||
<DashboardSplitPane>
|
||||
<Sidebar />
|
||||
<DashboardContent />
|
||||
</DashboardSplitPane>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
// @ts-nocheck
|
||||
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' },
|
||||
};
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
* Sideabr head.
|
||||
* Sidebar head.
|
||||
*/
|
||||
function SidebarHeadJSX({
|
||||
// #withCurrentOrganization
|
||||
@@ -25,39 +16,12 @@ function SidebarHeadJSX({
|
||||
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>
|
||||
<div className="title">{organization.name}</div>
|
||||
<span class="subtitle">{user.full_name}</span>
|
||||
</div>
|
||||
|
||||
<div className="sidebar__head-logo">
|
||||
<Icon
|
||||
icon={'mini-bigcapital'}
|
||||
width={28}
|
||||
height={28}
|
||||
className="bigcapital--alt"
|
||||
/>
|
||||
<span className="bigcapital--alt">BC</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Tooltip, Position, Spinner } from '@blueprintjs/core';
|
||||
import { useWorkspaces } from '@/hooks/query';
|
||||
import { useAuthOrganizationId } from '@/hooks/state';
|
||||
import { useSwitchOrganization } from '@/hooks/useSwitchOrganization';
|
||||
import { firstLettersArgs } from '@/utils';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import '@/style/containers/Dashboard/WorkspacesSidebar.scss';
|
||||
|
||||
/**
|
||||
* Single workspace icon button.
|
||||
*/
|
||||
function WorkspaceIcon({ workspace, isActive, onClick }) {
|
||||
const name = workspace.metadata?.name || workspace.organizationId;
|
||||
const initials = firstLettersArgs(...(name || '').split(' '));
|
||||
const isDisabled = !workspace.isReady || workspace.isBuildRunning;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={name}
|
||||
position={Position.RIGHT}
|
||||
minimal
|
||||
className="workspaces-sidebar__item-tooltip"
|
||||
>
|
||||
<button
|
||||
className={classNames('workspaces-sidebar__item', {
|
||||
'is-active': isActive,
|
||||
'is-disabled': isDisabled,
|
||||
})}
|
||||
onClick={() => !isDisabled && onClick(workspace.organizationId)}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{workspace.isBuildRunning ? (
|
||||
<Spinner size={16} />
|
||||
) : (
|
||||
<span className="workspaces-sidebar__item-initials">{initials}</span>
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Workspaces sidebar container.
|
||||
*/
|
||||
export function WorkspacesSidebar() {
|
||||
const { data: workspaces, isLoading } = useWorkspaces();
|
||||
const activeOrganizationId = useAuthOrganizationId();
|
||||
const switchOrganization = useSwitchOrganization();
|
||||
|
||||
return (
|
||||
<div className="workspaces-sidebar">
|
||||
<div className="workspaces-sidebar__list">
|
||||
{isLoading ? (
|
||||
<div className="workspaces-sidebar__loading">
|
||||
<Spinner size={20} />
|
||||
</div>
|
||||
) : (
|
||||
workspaces?.map((workspace) => (
|
||||
<WorkspaceIcon
|
||||
key={workspace.organizationId}
|
||||
workspace={workspace}
|
||||
isActive={workspace.organizationId === activeOrganizationId}
|
||||
onClick={switchOrganization}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export * from './exchangeRates';
|
||||
export * from './contacts';
|
||||
export * from './subscriptions';
|
||||
export * from './organization';
|
||||
export * from './workspaces';
|
||||
export * from './landedCost';
|
||||
export * from './UniversalSearch/UniversalSearch';
|
||||
export * from './GenericResource';
|
||||
|
||||
22
packages/webapp/src/hooks/query/workspaces.tsx
Normal file
22
packages/webapp/src/hooks/query/workspaces.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
// @ts-nocheck
|
||||
import { useRequestQuery } from '../useQueryRequest';
|
||||
|
||||
/**
|
||||
* Retrieve workspaces of the authenticated user.
|
||||
*/
|
||||
export function useWorkspaces(props) {
|
||||
return useRequestQuery(
|
||||
['workspaces'],
|
||||
{ method: 'get', url: 'workspaces' },
|
||||
{
|
||||
select: (res) => res.data.workspaces,
|
||||
initialDataUpdatedAt: 0,
|
||||
initialData: {
|
||||
data: {
|
||||
workspaces: [],
|
||||
},
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
25
packages/webapp/src/hooks/useSwitchOrganization.tsx
Normal file
25
packages/webapp/src/hooks/useSwitchOrganization.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
// @ts-nocheck
|
||||
import { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { setCookie } from '@/utils';
|
||||
import { setOrganizationId } from '@/store/authentication/authentication.actions';
|
||||
|
||||
/**
|
||||
* Switches the active organization by updating the cookie,
|
||||
* Redux state, clearing the query cache, and reloading the app.
|
||||
*/
|
||||
export function useSwitchOrganization() {
|
||||
const dispatch = useDispatch();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useCallback(
|
||||
(organizationId: string) => {
|
||||
setCookie('organization_id', organizationId);
|
||||
dispatch(setOrganizationId(organizationId));
|
||||
queryClient.removeQueries();
|
||||
window.location.assign('/');
|
||||
},
|
||||
[dispatch, queryClient],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
@import 'src/style/_base.scss';
|
||||
|
||||
.workspaces-sidebar {
|
||||
width: 64px;
|
||||
height: 100vh;
|
||||
background: $sidebar-background;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.05);
|
||||
z-index: $sidebar-zindex + 1;
|
||||
|
||||
&__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__item {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
border: 0;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease, transform 0.1s ease, border-radius 0.15s ease;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: $sidebar-background;
|
||||
border-radius: 12px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 3px;
|
||||
height: 24px;
|
||||
background: #fff;
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bp4-spinner {
|
||||
.bp4-spinner-head {
|
||||
stroke: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active .bp4-spinner .bp4-spinner-head {
|
||||
stroke: $sidebar-background;
|
||||
}
|
||||
}
|
||||
|
||||
&__item-initials {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&__loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 20px;
|
||||
|
||||
.bp4-spinner .bp4-spinner-head {
|
||||
stroke: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,15 @@
|
||||
@import '../../_base.scss';
|
||||
$dashboard-views-bar-height: 44px;
|
||||
|
||||
.dashboard-layout {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
|
||||
> .split-pane {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
|
||||
Reference in New Issue
Block a user