Compare commits

...

79 Commits

Author SHA1 Message Date
elforjani13
754618aa7a fix edit project. 2022-07-15 20:56:52 +02:00
elforjani13
50c905eabb feat: add empty status & fix edit project. 2022-07-15 20:46:04 +02:00
elforjani13
709e06a646 feat: add project api 2022-07-14 18:29:22 +02:00
elforjani13
8826d2bc5b fix: notes. 2022-07-10 17:41:50 +02:00
elforjani13
38a961b899 fix: additional notes. 2022-07-06 13:44:00 +02:00
elforjani13
7ef7e126e5 fix: rename time entry form to project time entry form. 2022-07-06 13:42:15 +02:00
elforjani13
bcf0ec25b8 fix: rename task form to project task form. 2022-07-06 13:41:17 +02:00
elforjani13
965a8966f6 fix: rename expense form to project expense form. 2022-07-06 13:39:33 +02:00
elforjani13
b030d6ea37 fix: add estimated & expense dialog. 2022-07-04 11:14:24 +02:00
elforjani13
31fef21362 feat: add purchases & sales tables. 2022-06-30 22:05:35 +02:00
elforjani13
6f2a456a56 feat: add expense form. 2022-06-30 22:03:50 +02:00
elforjani13
6134ad5598 feat: add estimated expense. 2022-06-30 21:59:12 +02:00
elforjani13
cd08d0ee16 feat: add transaction select. 2022-06-23 21:51:34 +02:00
elforjani13
f268b8a95a feat: project status. 2022-06-23 19:41:09 +02:00
elforjani13
6a06950654 feat: project status. 2022-06-23 19:37:29 +02:00
elforjani13
d9de3341fe feat: add timesheet header. 2022-06-23 17:50:14 +02:00
elforjani13
6b6081e32e feat: add project & timesheet table. 2022-06-23 00:15:47 +02:00
elforjani13
7be568b8ac feat: time entry dialog. 2022-06-23 00:14:49 +02:00
elforjani13
50522af72d fix: task form dialog. 2022-06-23 00:13:46 +02:00
elforjani13
0b454d6d4d feat: project table. 2022-06-23 00:12:17 +02:00
elforjani13
4ba64cc4ff feat: add project timesheet. 2022-06-23 00:10:06 +02:00
elforjani13
5128c021b0 fix: project form. 2022-06-23 00:07:21 +02:00
elforjani13
5a8fcc8fb5 feat: add time entry form. 2022-06-15 16:09:21 +02:00
elforjani13
9cf1b993dd feat: add time entry form. 2022-06-15 16:08:50 +02:00
elforjani13
f443a1b106 fix: project detail tabs. 2022-06-15 11:38:20 +02:00
elforjani13
0eb0aee1ef feat: project detail tabs. 2022-06-14 17:19:59 +02:00
elforjani13
4b992c4bb4 fix: project details. 2022-06-13 17:55:52 +02:00
elforjani13
051681e6f3 feat: add timesheet & project details. 2022-06-13 17:33:54 +02:00
Ahmed Bouhuolia
629c790430 Merge pull request #57 from bigcapitalhq/BIG-379-create-a-project
`BIG-379` Add project & task dialog & projects list.
2022-06-12 13:07:29 +02:00
elforjani13
bdadc5d795 fix: remove the inner container. 2022-06-12 12:55:37 +02:00
elforjani13
23bb9c4cc3 fix: rename project & task form dialog. 2022-06-12 12:43:03 +02:00
elforjani13
8136378725 fix: project & task dialog. 2022-06-12 11:56:06 +02:00
elforjani13
4eac2239b1 feat: add projects view tabs. 2022-06-12 09:43:19 +02:00
elforjani13
a44f548ff9 feat: projects actions bar. 2022-06-11 15:30:11 +02:00
elforjani13
327916da4b fix: add FDateInput 2022-06-11 13:58:04 +02:00
elforjani13
bee7896279 fix: project form. 2022-06-11 13:29:33 +02:00
elforjani13
cb0a315ca6 feat: add task form dialog. 2022-06-11 00:45:31 +02:00
elforjani13
d2c907541a feat: add project form dialog. 2022-06-11 00:38:00 +02:00
elforjani13
928d4d3f00 feat: add projects list. 2022-06-11 00:36:18 +02:00
Ahmed Bouhuolia
01038136f2 Update CHANGELOG.md 2022-04-23 00:41:18 +02:00
a.bouhuolia
1172e69d96 chore: add 1.7.4-rc.2 CHANGELOG. 2022-04-23 00:37:18 +02:00
a.bouhuolia
87758bf773 chore(Sidebar): docs. 2022-04-23 00:28:26 +02:00
a.bouhuolia
5cbb3c84e6 Merge branch 'BIG-374-refactoring-sidebar-menu-with-feature-and-permissions-control' into develop 2022-04-22 23:42:45 +02:00
a.bouhuolia
52924383bd feat(Sidebar): filter sidebar items based on subscription state. 2022-04-18 01:24:11 +02:00
a.bouhuolia
8d1825a065 feature(Sidebar): BIG-374 filtering the sidebar items based on each item feature support. 2022-04-18 00:16:37 +02:00
a.bouhuolia
5e4e9c37c3 feat(Sidebar): add the missing sidebar items. 2022-04-17 05:19:23 +02:00
a.bouhuolia
944bc29f4d feat(Sidebar): Refactoring sidebar menu with feature and permissions abilities control. 2022-04-17 05:05:35 +02:00
a.bouhuolia
682b296f7c fix(FlexGrid): BIG-378 Reports drawers columns css conflict. 2022-04-15 22:33:08 +02:00
Ahmed Bouhuolia
e662bf7af9 Update CHANGELOG.md 2022-04-15 06:34:18 +02:00
a.bouhuolia
a829ceb709 Merge branch 'develop' into main 2022-04-15 06:29:34 +02:00
a.bouhuolia
cdce00187b Merge branch 'feature/tooltip-oneline' into develop 2022-04-15 06:26:42 +02:00
a.bouhuolia
91a38b34cc feat: add readonly entriese details as oneline with tooltip for more details. 2022-04-15 06:24:24 +02:00
a.bouhuolia
1b97a162e8 Merge branch 'BIG-entries-columns' of https://github.com/bigcapitalhq/client into develop 2022-04-15 04:36:08 +02:00
elforjani13
e8e12e63ea feat: add tooltip cell to detail. 2022-04-10 13:46:05 +02:00
elforjani13
80feba6005 feat(sales/purchases): add tooltip cell to detail. 2022-04-09 02:04:31 +02:00
elforjani13
cc457e1e43 feat: estimate tooltip. 2022-04-09 00:42:34 +02:00
a.bouhuolia
2ced5dc013 feat(TextOverlayTooltip): WIP 2022-04-08 06:04:12 +02:00
Ahmed Bouhuolia
dd86c2993e Merge pull request #50 from bigcapitalhq/BIG-372-activate-branches-and-warehouses-dialog-reloading-once-activating
BIG-372: activate branches & warehouses reloading.
2022-04-08 05:21:31 +02:00
a.bouhuolia
bd05a4a188 fix(GeneralLedger): BIG-373 Issue general ledger report select specific account. 2022-04-08 05:19:11 +02:00
elforjani13
8160cbe402 feat: add entries-columns. 2022-04-06 16:52:44 +02:00
elforjani13
0ef6bebfb8 BIG-372: activate branches & warehouses reloading. 2022-04-06 15:00:30 +02:00
a.bouhuolia
91ff3fdccb Merge branch 'main' into develop 2022-04-05 06:08:54 +02:00
a.bouhuolia
edd37fff78 fix(PaymaentViaVocher): make the plan is fixed. 2022-04-05 06:06:02 +02:00
a.bouhuolia
679f7ce96c Merge branch 'develop' into main 2022-04-05 05:27:28 +02:00
a.bouhuolia
5eb9968095 Merge branch 'develop' into main 2022-03-31 15:09:25 +02:00
a.bouhuolia
5c601fcf2d fix(Account): BIG-296 Issue when creating a new child account from chart of accounts list. 2022-03-28 11:58:12 +02:00
a.bouhuolia
df4c4a832b Merge branch 'develop' into main 2022-03-24 15:32:30 +02:00
a.bouhuolia
98a02396a9 Merge branch 'develop' into main 2022-02-21 15:02:35 +02:00
a.bouhuolia
96635ffa84 Merge branch 'develop' into main 2022-02-18 20:44:42 +02:00
a.bouhuolia
68c0678dc3 Merge branch 'develop' into main 2022-02-17 12:10:20 +02:00
a.bouhuolia
da699a766a Merge branch 'develop' into main 2022-01-13 15:40:55 +02:00
a.bouhuolia
f1899e1ce1 Merge branch 'develop' into main 2022-01-08 18:20:08 +02:00
Ahmed Bouhuolia
11851d114d Merge pull request #16 from bigcapitalhq/develop
Release 1.5.5
2022-01-04 22:35:49 +02:00
a.bouhuolia
21779007be fix: application version. 2022-01-03 23:14:11 +02:00
a.bouhuolia
4fc1ecdc2d Merge branch 'main' of https://github.com/bigcapitalhq/client into main 2022-01-03 19:42:48 +02:00
a.bouhuolia
c9b5cecf7a Merge branch 'develop' into main 2022-01-03 19:42:23 +02:00
elforjani13
c31e9dcd29 fix: inventory adjustment & contacts drawer. 2022-01-03 14:35:50 +02:00
elforjani13
430ab95dc3 landed cost localiztion. 2022-01-03 13:38:22 +02:00
Ahmed Bouhuolia
8100a57195 Merge pull request #15 from bigcapitalhq/develop
Merge `develop` to `main`
2022-01-03 12:31:14 +02:00
170 changed files with 6840 additions and 1356 deletions

View File

@@ -2,6 +2,18 @@
All notable changes to Bigcapital server-side will be in this file.
## [1.7.4-rc.2] - 20-04-2022
### Fixed
- `BIG-374` Refactoring sidebar men with ability permissions and feature control on each item.
## [1.7.3-rc.2] - 15-04-2022
### Fixed
- `BIG-372` Activate branches and warehouses dialog reloading once activating.
- `BIG-373` Issue general ledger report select specific account.
- `BIG-377` Make readonly details entries as oneline with tooltip for more details.
## [1.7.2-rc.2] - 04-04-2022
### Fixed

View File

@@ -85,6 +85,9 @@ function getClientEnvironment(publicUrl) {
WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST,
WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
// Application version.
VERSION: paths.appVersion
}
);
// Stringify all values so we can feed into webpack DefinePlugin

View File

@@ -48,6 +48,8 @@ const resolveModule = (resolveFn, filePath) => {
return resolveFn(`${filePath}.js`);
};
const appVersion = require(resolveApp('package.json')).version;
// config after eject: we're in ./config/
module.exports = {
dotenv: resolveApp('.env'),
@@ -65,6 +67,7 @@ module.exports = {
proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'),
publicUrlOrPath,
appVersion
};

42
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "bigcapital-client",
"version": "1.5.8",
"version": "1.7.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1043,17 +1043,6 @@
"to-fast-properties": "^2.0.0"
}
},
"@blueprintjs-formik/core": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/core/-/core-0.1.5.tgz",
"integrity": "sha512-H0aXiNMYC8RwhWR1F2O77dcRcRITijUX5we51G4AK2Vmp1yXCmNb0piN9ftsjL5vVIvIsMKWg+dfbwREmB5VWg==",
"requires": {
"lodash.get": "^4.4.2",
"lodash.keyby": "^4.6.0",
"styled-components": "^5.3.3",
"web-vitals": "^2.1.4"
}
},
"@blueprintjs-formik/select": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/select/-/select-0.1.4.tgz",
@@ -1980,6 +1969,16 @@
"integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==",
"dev": true
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dev": true,
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/http-proxy": {
"version": "1.17.3",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.3.tgz",
@@ -2240,6 +2239,25 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
},
"@types/styled-components": {
"version": "5.1.25",
"resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.25.tgz",
"integrity": "sha512-fgwl+0Pa8pdkwXRoVPP9JbqF0Ivo9llnmsm+7TCI330kbPIFd9qv1Lrhr37shf4tnxCOSu+/IgqM7uJXLWZZNQ==",
"dev": true,
"requires": {
"@types/hoist-non-react-statics": "*",
"@types/react": "*",
"csstype": "^3.0.2"
},
"dependencies": {
"csstype": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
"integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==",
"dev": true
}
}
},
"@types/testing-library__dom": {
"version": "6.12.1",
"resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.12.1.tgz",

View File

@@ -38,6 +38,7 @@
"cross-env": "^7.0.2",
"css-loader": "3.4.2",
"deep-map-keys": "^2.0.1",
"deepdash": "^5.3.9",
"dependency-graph": "^0.11.0",
"dotenv": "8.2.0",
"dotenv-expand": "5.1.0",
@@ -150,6 +151,7 @@
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/react-router-dom": "^5.1.8",
"@types/styled-components": "^5.1.25",
"@types/yup": "^0.29.13",
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
"compression-webpack-plugin": "^6.1.0",

View File

@@ -16,7 +16,11 @@ export const TABLES = {
CASHFLOW_Transactions: 'cashflow_transactions',
CREDIT_NOTES: 'credit_notes',
VENDOR_CREDITS: 'vendor_credits',
WAREHOUSE_TRANSFERS:'warehouse_transfers'
WAREHOUSE_TRANSFERS: 'warehouse_transfers',
PROJECTS: 'projects',
TIMESHEETS: 'timesheets',
PURCHASES: 'purchases',
SALES: 'sales',
};
export const TABLE_SIZE = {

View File

@@ -11,7 +11,7 @@ export const CommercialDocHeader = styled.div`
`;
export const CommercialDocTopHeader = styled.div`
margin-bottom: 25px;
margin-bottom: 30px;
`;
export const CommercialDocEntriesTable = styled(DataTable)`

View File

@@ -3,7 +3,7 @@ import { Switch, Route } from 'react-router';
import 'style/pages/Dashboard/Dashboard.scss';
import Sidebar from 'components/Sidebar/Sidebar';
import { Sidebar } from 'containers/Dashboard/Sidebar/Sidebar';
import DashboardContent from 'components/Dashboard/DashboardContent';
import DialogsContainer from 'components/DialogsContainer';
import PreferencesPage from 'components/Preferences/PreferencesPage';

View File

@@ -197,6 +197,7 @@ export default function DataTable(props) {
DataTable.defaultProps = {
pagination: false,
hidePaginationNoPages: true,
hideTableHeader: false,
size: null,
spinnerProps: { size: 30 },

View File

@@ -0,0 +1,27 @@
import React from 'react';
import { Tooltip, Position } from '@blueprintjs/core';
/**
* Text overview tooltip cell.
* @returns {JSX.Element}
*/
export function TextOverviewTooltipCell({ cell: { value } }) {
const SUBMENU_POPOVER_MODIFIERS = {
flip: { boundariesElement: 'viewport', padding: 20 },
offset: { offset: '0, 10' },
preventOverflow: { boundariesElement: 'viewport', padding: 40 },
};
return (
<Tooltip
content={value}
position={Position.BOTTOM_LEFT}
boundary={'viewport'}
minimal={true}
modifiers={SUBMENU_POPOVER_MODIFIERS}
targetClassName={'table-tooltip-overview-target'}
>
{value}
</Tooltip>
);
}

View File

@@ -10,6 +10,7 @@ import CheckBoxFieldCell from './CheckBoxFieldCell';
import SwitchFieldCell from './SwitchFieldCell';
import TextAreaCell from './TextAreaCell';
import BranchesListFieldCell from './BranchesListFieldCell';
import { TextOverviewTooltipCell } from './TextOverviewTooltipCell';
export {
AccountsListFieldCell,
@@ -25,4 +26,5 @@ export {
SwitchFieldCell,
TextAreaCell,
BranchesListFieldCell,
TextOverviewTooltipCell,
};

View File

@@ -80,12 +80,23 @@ function TableHeaderGroup({ headerGroup }) {
export default function TableHeader() {
const {
table: { headerGroups, page },
props: { TableHeaderSkeletonRenderer, headerLoading, progressBarLoading },
props: {
TableHeaderSkeletonRenderer,
headerLoading,
progressBarLoading,
hideTableHeader,
},
} = useContext(TableContext);
// Can't contiunue if the thead is disabled.
if (hideTableHeader) {
return null;
}
if (headerLoading && TableHeaderSkeletonRenderer) {
return <TableHeaderSkeletonRenderer />;
}
return (
<ScrollSyncPane>
<div className="thead">

View File

@@ -40,6 +40,11 @@ import BranchActivateDialog from '../containers/Dialogs/BranchActivateDialog';
import WarehouseActivateDialog from '../containers/Dialogs/WarehouseActivateDialog';
import CustomerOpeningBalanceDialog from '../containers/Dialogs/CustomerOpeningBalanceDialog';
import VendorOpeningBalanceDialog from '../containers/Dialogs/VendorOpeningBalanceDialog';
import ProjectFormDialog from '../containers/Projects/containers/ProjectFormDialog';
import ProjectTaskFormDialog from '../containers/Projects/containers/ProjectTaskFormDialog';
import ProjectTimeEntryFormDialog from '../containers/Projects/containers/ProjectTimeEntryFormDialog';
import ProjectExpenseForm from '../containers/Projects/containers/ProjectExpenseForm';
import EstimatedExpenseFormDialog from '../containers/Projects/containers/EstimatedExpenseFormDialog';
/**
* Dialogs container.
@@ -90,6 +95,11 @@ export default function DialogsContainer() {
<WarehouseActivateDialog dialogName={'warehouse-activate'} />
<CustomerOpeningBalanceDialog dialogName={'customer-opening-balance'} />
<VendorOpeningBalanceDialog dialogName={'vendor-opening-balance'} />
<ProjectFormDialog dialogName={'project-form'} />
<ProjectTaskFormDialog dialogName={'project-task-form'} />
<ProjectTimeEntryFormDialog dialogName={'project-time-entry-form'} />
<ProjectExpenseForm dialogName={'project-expense-form'} />
<EstimatedExpenseFormDialog dialogName={'estimated-expense-form'} />
</div>
);
}

View File

@@ -0,0 +1,16 @@
import styled from 'styled-components';
import { FlexProps } from './interfaces';
export const FlexStyled = styled.div<FlexProps>`
display: flex;
justify-content: space-between;
flex-wrap: wrap;
align-items: ${({ align }) => align || 'center'};
&:after {
content: '';
max-width: ${({ col, gap = 1 }) =>
col && col < 12 ? `${(100 * col) / 12 - gap}%` : '100%'};
width: 100%;
}
`;

View File

@@ -0,0 +1,26 @@
import * as React from 'react';
import { FlexProps } from './interfaces';
import { FlexItem } from './FlexItem';
import { FlexStyled } from './Flex.style';
export function Flex({
children,
col = 12,
gap,
align,
className,
style,
}: FlexProps) {
return (
<FlexStyled
col={col}
gap={gap}
align={align}
className={className}
style={style}
>
{children}
<FlexItem col={col} gap={gap} />
</FlexStyled>
);
}

View File

@@ -0,0 +1,19 @@
import styled from 'styled-components';
import { ItemProps } from './interfaces';
export const FlexItem = styled.div<ItemProps>`
width: 100%;
max-width: ${({ col, gap = 1 }) =>
col && col < 12 ? `${(100 * col) / 12 - gap}%` : '100%'};
${({ marginBottom }) =>
marginBottom &&
`
margin-bottom: ${marginBottom}px;
`}
${({ stretch }) =>
stretch &&
`
display: flex;
align-self: stretch;
`}
`;

View File

@@ -0,0 +1 @@
export * from './FlexItem.style';

View File

@@ -0,0 +1,4 @@
export * from './Flex.style';
export * from './Flex';
export * from './FlexItem.style';
export * from './interfaces';

View File

@@ -0,0 +1,22 @@
import { HTMLAttributes, Component, StyleHTMLAttributes } from 'react';
export type Range = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
export interface ItemProps extends HTMLAttributes<HTMLDivElement> {
gap?: number;
col: Range;
marginBottom?: number;
stretch?: boolean;
as?: string | Component;
className?: string;
style?: StyleHTMLAttributes<HTMLDivElement>;
}
export interface FlexProps extends HTMLAttributes<HTMLDivElement> {
gap?: number;
align?: 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch';
col?: Range;
className?: string;
style?: StyleHTMLAttributes<HTMLDivElement>;
as?: string | Component;
}

View File

@@ -9,6 +9,7 @@ import {
TextArea,
} from '@blueprintjs-formik/core';
import { Select, MultiSelect } from '@blueprintjs-formik/select';
import { DateInput } from '@blueprintjs-formik/datetime';
export {
FormGroup as FFormGroup,
@@ -21,4 +22,5 @@ export {
MultiSelect as FMultiSelect,
EditableText as FEditableText,
TextArea as FTextArea,
DateInput as FDateInput,
};

View File

@@ -104,7 +104,7 @@ import {
// textClassName?: string;
// }
export default class MenuItem extends AbstractPureComponent2 {
export class MenuItem extends AbstractPureComponent2 {
static get defaultProps() {
return {
disabled: false,

View File

@@ -1,37 +0,0 @@
import React from 'react';
import SidebarContainer from 'components/Sidebar/SidebarContainer';
import SidebarHead from 'components/Sidebar/SidebarHead';
import SidebarMenu from 'components/Sidebar/SidebarMenu';
import { useGetSidebarMenu } from './utils';
import 'style/containers/Dashboard/Sidebar.scss';
export default function Sidebar({ dashboardContentRef }) {
const menu = useGetSidebarMenu();
return (
<SidebarContainer>
<SidebarHead />
<div className="sidebar__menu">
<SidebarMenu menu={menu} />
</div>
<SidebarFooterVersion />
</SidebarContainer>
);
}
/**
* Sidebar footer version.
* @returns {React.JSX}
*/
function SidebarFooterVersion() {
const { REACT_APP_VERSION: VERSION } = process.env;
if (!VERSION) {
return null;
}
return <div class="sidebar__version">v{VERSION}</div>;
}

View File

@@ -1,123 +0,0 @@
import React from 'react';
import { Menu, MenuDivider } from '@blueprintjs/core';
import { useHistory, useLocation } from 'react-router-dom';
import { Choose } from 'components';
import Icon from 'components/Icon';
import MenuItem from 'components/MenuItem';
import { MenuItemLabel } from 'components';
import classNames from 'classnames';
import SidebarOverlay from 'components/SidebarOverlay';
import { compose } from 'redux';
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
const DEFAULT_ITEM = {
text: '',
href: '',
};
function matchPath(pathname, path, matchExact) {
return matchExact ? pathname === path : pathname.indexOf(path) !== -1;
}
function SidebarMenuItemSpace({ space }) {
return <div class="bp3-menu-spacer" style={{ height: `${space}px` }} />;
}
function SidebarMenu({ menu, isSubscriptionActive }) {
const history = useHistory();
const location = useLocation();
const [isOpen, setIsOpen] = React.useState(false);
const [currentItem, setCurrentItem] = React.useState(null);
const menuItemsMapper = (list) => {
return list.map((item, index) => {
const hasChildren = Array.isArray(item.children);
const isActive =
(item.children
? item.children.some((c) =>
matchPath(location.pathname, c.href, item.matchExact),
)
: item.href &&
matchPath(location.pathname, item.href, item.matchExact)) ||
currentItem === item;
const handleItemClick = () => {
if (item.href) {
history.push(item.href);
}
if (item.children && item.children.length > 0) {
setIsOpen(true);
setCurrentItem(item);
} else {
setIsOpen(false);
}
};
return (
<Choose>
<Choose.When condition={item.spacer}>
<SidebarMenuItemSpace space={item.spacer} />
</Choose.When>
<Choose.When condition={item.divider}>
<MenuDivider key={index} title={item.title} />
</Choose.When>
<Choose.When condition={item.label}>
<MenuItemLabel key={index} text={item.text} />
</Choose.When>
<Choose.Otherwise>
<MenuItem
key={index}
active={isActive}
icon={<Icon icon={item.icon} iconSize={item.iconSize} />}
text={item.text}
disabled={item.disabled}
dropdownType={item.dropdownType || 'collapse'}
caretIconSize={16}
onClick={handleItemClick}
callapseActive={!!isActive}
itemClassName={classNames({
'is-active': isActive,
'has-icon': !hasChildren && item.icon,
})}
hasSubmenu={hasChildren}
/>
</Choose.Otherwise>
</Choose>
);
});
};
const filterItems = menu.filter(
(item) => isSubscriptionActive || item.enableBilling,
);
const items = menuItemsMapper(filterItems);
const handleSidebarOverlayClose = () => {
setIsOpen(false);
};
return (
<div>
<Menu className="sidebar-menu">{items}</Menu>{' '}
<SidebarOverlay
isOpen={isOpen}
label={currentItem?.text || ''}
items={currentItem?.children || []}
onClose={handleSidebarOverlayClose}
/>
</div>
);
}
export default compose(
withSubscriptions(
({ isSubscriptionActive }) => ({ isSubscriptionActive }),
'main',
),
)(SidebarMenu);

View File

@@ -1,48 +0,0 @@
import sidebarMenuList from 'config/sidebarMenu';
import { isArray, isEmpty } from 'lodash';
import { useAbilityContext } from 'hooks/utils';
export function useGetSidebarMenu() {
const ability = useAbilityContext();
return sidebarMenuList
.map((item) => {
const children = isArray(item.children)
? item.children.filter((childItem) => {
return isArray(childItem.permission)
? childItem.permission.some((perm) =>
ability.can(perm.ability, perm.subject),
)
: childItem?.permission?.ability && childItem?.permission?.subject
? ability.can(
childItem.permission.ability,
childItem.permission.subject,
)
: true;
})
: [];
return {
...item,
...(isArray(item.children)
? {
children,
}
: {}),
};
})
.filter((item) => {
return isArray(item.permission)
? item.permission.some((per) =>
ability.can(per.ability, per.subject),
)
: item?.permission?.ability && item?.permission?.subject
? ability.can(item.permission.ability, item.permission.subject)
: true;
})
.filter((item) =>
isEmpty(item.children) && !item.href && !item.label && !item.divider
? false
: true,
);
}

View File

@@ -1,146 +0,0 @@
import React from 'react';
import { Overlay } from '@blueprintjs/core';
import { Link } from 'react-router-dom';
import SidebarOverlayContainer from './SidebarOverlayContainer';
interface ISidebarOverlayItem {
text: string;
href: string;
divider?: boolean;
label?: boolean;
}
interface ISidebarOverlayProps {
isOpen: boolean;
items: ISidebarOverlayItem[];
label: string;
onClose: Function;
}
interface ISidebarOverlayItemProps {
text: string;
href: string;
onLinkClick: Function;
}
interface ISidebarOverlayItemDivider {
divider: boolean;
}
/**
* Sidebar overlay item.
*/
function SidebarOverlayItem({
text,
href,
onLinkClick,
}: ISidebarOverlayItemProps) {
const handleLinkClick = () => {
onLinkClick && onLinkClick();
};
return (
<div className="sidebar-overlay__item">
<Link onClick={handleLinkClick} to={href}>
{text}
</Link>
</div>
);
}
interface ISidebarOverlayItemLabel {
text: string;
}
function SidebarOverlayLabel({ text }: ISidebarOverlayItemLabel) {
return <div className="sidebar-overlay__label">{text}</div>;
}
function SidebarOverlayDivider() {
return <div className={'sidebar-overlay__divider'}></div>;
}
/**
* Sidebar overlay component.
*/
export default function SidebarOverlay({
label,
isOpen: controllerdIsOpen,
onClose,
items,
}: ISidebarOverlayProps) {
const [isEverOpened, setEverOpened] = React.useState(false);
const [isOpen, setIsOpen] = React.useState(controllerdIsOpen);
React.useEffect(() => {
if (
typeof controllerdIsOpen !== 'undefined' &&
isOpen !== controllerdIsOpen
) {
setIsOpen(controllerdIsOpen);
}
}, [controllerdIsOpen, setIsOpen, isOpen]);
React.useEffect(() => {
if (isOpen && !isEverOpened) {
setEverOpened(true);
}
}, [isEverOpened, isOpen]);
if (!isEverOpened) {
return '';
}
// Handle overlay close event.
const handleOverlayClose = () => {
setIsOpen(false);
onClose && onClose();
};
// Handle overlay open event.
const handleOverlayOpen = () => {
setIsOpen(true);
};
// Handle sidebar item link click.
const handleItemClick = () => {
setIsOpen(false);
onClose && onClose();
};
return (
<Overlay
isOpen={isOpen}
portalContainer={
(document.querySelector('.Pane.vertical.Pane2') as HTMLElement) ||
document.body
}
onClose={handleOverlayClose}
onOpening={handleOverlayOpen}
transitionDuration={100}
backdropClassName={'sidebar-overlay-backdrop'}
>
<div className="sidebar-overlay sidebar-overlay-transition">
<SidebarOverlayContainer>
<div className="sidebar-overlay__menu">
{label && (
<>
<SidebarOverlayLabel text={label} />
<SidebarOverlayDivider />
</>
)}
{items.map((item) =>
item.divider ? (
<SidebarOverlayDivider />
) : item.label ? (
<SidebarOverlayLabel text={item.text} />
) : (
<SidebarOverlayItem
onLinkClick={handleItemClick}
text={item.text}
href={item.href}
/>
),
)}
</div>
</SidebarOverlayContainer>
</div>
</Overlay>
);
}

View File

@@ -49,7 +49,6 @@ import DrawerHeaderContent from './Drawer/DrawerHeaderContent';
import Postbox from './Postbox';
import AccountsSuggestField from './AccountsSuggestField';
import MaterialProgressBar from './MaterialProgressBar';
import { MoneyFieldCell } from './DataTableCells';
import AvaterCell from './AvaterCell';
import { ItemsMultiSelect } from './Items';
@@ -102,9 +101,12 @@ export * from './ExchangeRate';
export * from './Branches';
export * from './Warehouses';
export * from './Currencies';
export * from './FormTopbar'
export * from './FormTopbar';
export * from './Paper';
export * from './Accounts'
export * from './Accounts';
export * from './DataTableCells';
export * from './FlexGrid';
export * from './MenuItem';
const Hint = FieldHint;
@@ -167,7 +169,6 @@ export {
Postbox,
AccountsSuggestField,
MaterialProgressBar,
MoneyFieldCell,
ItemsMultiSelect,
AvaterCell,
MoreMenuItems,

File diff suppressed because it is too large Load Diff

View File

@@ -57,9 +57,7 @@ function BillTransactionDeleteAlert({
loading={isLoading}
>
<p>
<T
id={`Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?`}
/>
<T id={`landed_cost.once_your_delete_this_located_landed_cost`} />
</p>
</Alert>
);

View File

@@ -23,6 +23,7 @@ import TransactionsLockingAlerts from '../TransactionsLocking/TransactionsLockin
import WarehousesAlerts from '../Preferences/Warehouses/WarehousesAlerts';
import WarehousesTransfersAlerts from '../WarehouseTransfers/WarehousesTransfersAlerts';
import BranchesAlerts from '../Preferences/Branches/BranchesAlerts';
import ProjectAlerts from '../../containers/Projects/containers/ProjectAlerts';
export default [
...AccountsAlerts,
@@ -50,4 +51,5 @@ export default [
...WarehousesAlerts,
...WarehousesTransfersAlerts,
...BranchesAlerts,
...ProjectAlerts,
];

View File

@@ -79,6 +79,10 @@ export const handleCashFlowTransactionType = (reference, openDrawer) => {
return openDrawer('refund-vendor-detail-drawer', {
refundTransactionId: reference.reference_id,
});
case 'InventoryAdjustment':
return openDrawer('inventory-adjustment-drawer', {
inventoryId: reference.reference_id,
});
default:
return openDrawer('cashflow-transaction-drawer', {

View File

@@ -0,0 +1,42 @@
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>;
}

View File

@@ -1,19 +1,21 @@
import React, { useEffect } from 'react';
import { Scrollbar } from 'react-scrollbars-custom';
import classNames from 'classnames';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withDashboard from 'containers/Dashboard/withDashboard';
import withSubscriptions from 'containers/Subscriptions/withSubscriptions';
import { useObserveSidebarExpendedBodyclass } from './hooks';
import { compose } from 'utils';
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
function SidebarContainer({
/**
* Sidebar container/
* @returns {JSX.Element}
*/
function SidebarContainerJSX({
// #ownProps
children,
// #withDashboardActions
toggleSidebarExpend,
// #withDashboard
sidebarExpended,
@@ -22,9 +24,10 @@ function SidebarContainer({
}) {
const sidebarScrollerRef = React.useRef();
useEffect(() => {
document.body.classList.toggle('has-mini-sidebar', !sidebarExpended);
// Toggles classname to body once sidebar expend/shrink.
useObserveSidebarExpendedBodyclass(sidebarExpended);
useEffect(() => {
if (!sidebarExpended && sidebarScrollerRef.current) {
sidebarScrollerRef.current.scrollTo({
top: 0,
@@ -39,9 +42,9 @@ function SidebarContainer({
}
};
const scrollerElementRef = (ref) => {
const scrollerElementRef = React.useCallback((ref) => {
sidebarScrollerRef.current = ref;
};
}, []);
return (
<div
@@ -64,8 +67,7 @@ function SidebarContainer({
);
}
export default compose(
withDashboardActions,
export const SidebarContainer = compose(
withDashboard(({ sidebarExpended }) => ({
sidebarExpended,
})),
@@ -73,4 +75,4 @@ export default compose(
({ isSubscriptionActive }) => ({ isSubscriptionActive }),
'main',
),
)(SidebarContainer);
)(SidebarContainerJSX);

View File

@@ -1,9 +1,11 @@
import React from 'react';
import { Button, Popover, Menu, Position } from '@blueprintjs/core';
import Icon from 'components/Icon';
import { Icon } from 'components';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import { useAuthenticatedAccount } from 'hooks/query';
import { compose, firstLettersArgs } from 'utils';
import withCurrentOrganization from '../../containers/Organization/withCurrentOrganization';
import { useAuthenticatedAccount } from '../../hooks/query';
// Popover modifiers.
const POPOVER_MODIFIERS = {
@@ -13,7 +15,7 @@ const POPOVER_MODIFIERS = {
/**
* Sideabr head.
*/
function SidebarHead({
function SidebarHeadJSX({
// #withCurrentOrganization
organization,
}) {
@@ -61,6 +63,6 @@ function SidebarHead({
);
}
export default compose(
export const SidebarHead = compose(
withCurrentOrganization(({ organization }) => ({ organization })),
)(SidebarHead);
)(SidebarHeadJSX);

View File

@@ -0,0 +1,74 @@
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);

View File

@@ -0,0 +1,358 @@
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);
}
/**
* 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);
}

View File

@@ -0,0 +1,78 @@
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',
}

View File

@@ -0,0 +1,13 @@
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);
}

View File

@@ -0,0 +1,16 @@
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);

View File

@@ -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} 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>
);
}

View File

@@ -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);

View File

@@ -1,14 +1,16 @@
import React from 'react';
import { Scrollbar } from 'react-scrollbars-custom';
interface ISidebarOverlayContainerProps {
children: JSX.Element | JSX.Element[],
export interface ISidebarOverlayContainerProps {
children: JSX.Element | JSX.Element[];
}
/**
* Sidebar overlay container.
*/
export default function SidebarOverlayContainer({ children }: ISidebarOverlayContainerProps) {
export function SidebarOverlayContainer({
children,
}: ISidebarOverlayContainerProps) {
return (
<div className={'sidebar-overlay__scroll-wrapper'}>
<Scrollbar noDefaultStyles={true}>

View File

@@ -0,0 +1,3 @@
export * from './SidebarOverlay';
export * from './SidebarOverlayContainer';
export * from './SidebarOverlayBinded'

View File

@@ -37,7 +37,7 @@ function AllocateLandedCostFloatingActions({
<DialogFooterActions alignment={'left'}>
{costTransactionEntry && (
<UnallocatedAmount>
Unallocated cost Amount:{' '}
<T id={'landed_cost.dialog.label_unallocated_cost_amount'}/>
<strong>{formattedUnallocatedCostAmount}</strong>
</UnallocatedAmount>
)}

View File

@@ -42,7 +42,10 @@ function AllocateLandedCostForm({
.map((entry) => transformToForm(entry, defaultInitialValues.items[0]));
if (entries.length <= 0) {
AppToaster.show({ message: 'Something wrong!', intent: Intent.DANGER });
AppToaster.show({
message: intl.get('something_wrong'),
intent: Intent.DANGER,
});
return;
}
const form = {
@@ -69,13 +72,14 @@ function AllocateLandedCostForm({
)
) {
AppToaster.show({
message:
'The total located cost is bigger than the transaction line.',
message: intl.get(
'landed_cost.error.the_total_located_cost_is_bigger_than_the_transaction_line',
),
intent: Intent.DANGER,
});
} else {
AppToaster.show({
message: 'Something went wrong!',
message: intl.get('something_went_wrong'),
intent: Intent.DANGER,
});
}

View File

@@ -9,7 +9,7 @@ const BranchActivateContext = React.createContext();
* Branch activate form provider.
*/
function BranchActivateFormProvider({ dialogName, ...props }) {
const { mutateAsync: activateBranches, isLoading } = useActivateBranches();
const { mutateAsync: activateBranches } = useActivateBranches();
// State provider.
const provider = {
@@ -18,7 +18,7 @@ function BranchActivateFormProvider({ dialogName, ...props }) {
};
return (
<DialogContent isLoading={isLoading}>
<DialogContent>
<BranchActivateContext.Provider value={provider} {...props} />
</DialogContent>
);

View File

@@ -36,7 +36,7 @@ function PaymentViaLicenseDialogContent({
setSubmitting(true);
const mutateValues = {
plan_slug: `${values.plan_slug}-${values.period}ly`,
plan_slug: `essentials-monthly`,
license_code: values.license_code,
};
// Payment via voucher mutate.

View File

@@ -9,8 +9,7 @@ const WarehouseActivateContext = React.createContext();
* warehouse activate form provider.
*/
function WarehouseActivateFormProvider({ dialogName, ...props }) {
const { mutateAsync: activateWarehouses, isLoading } =
useActivateWarehouses();
const { mutateAsync: activateWarehouses } = useActivateWarehouses();
// State provider.
const provider = {
@@ -19,7 +18,7 @@ function WarehouseActivateFormProvider({ dialogName, ...props }) {
};
return (
<DialogContent isLoading={isLoading}>
<DialogContent>
<WarehouseActivateContext.Provider value={provider} {...props} />
</DialogContent>
);

View File

@@ -13,57 +13,81 @@ import {
} from '@blueprintjs/core';
import {
FormatNumberCell,
TextOverviewTooltipCell,
FormattedMessage as T,
Choose,
Icon,
} from '../../../components';
import { getColumnWidth } from 'utils';
import { useBillDrawerContext } from './BillDrawerProvider';
/**
* Retrieve bill readonly details entries table columns.
*/
export const useBillReadonlyEntriesTableColumns = () =>
React.useMemo(
export const useBillReadonlyEntriesTableColumns = () => {
const {
bill: { entries },
} = useBillDrawerContext();
return React.useMemo(
() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
Cell: TextOverviewTooltipCell,
width: 150,
className: 'item',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('description'),
accessor: 'description',
Cell: TextOverviewTooltipCell,
className: 'description',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(entries, 'quantity', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(entries, 'rate', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(entries, 'amount', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
],
[],
);
};
/**
* Bill details status.

View File

@@ -10,56 +10,81 @@ import {
Tag,
Intent,
} from '@blueprintjs/core';
import { getColumnWidth } from 'utils';
import {
Icon,
FormattedMessage as T,
TextOverviewTooltipCell,
FormatNumberCell,
Choose,
} from '../../../components';
import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvider';
export const useCreditNoteReadOnlyEntriesColumns = () =>
React.useMemo(
export const useCreditNoteReadOnlyEntriesColumns = () => {
// credit note details drawer context.
const {
creditNote: { entries },
} = useCreditNoteDetailDrawerContext();
return React.useMemo(
() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
Cell: TextOverviewTooltipCell,
width: 150,
className: 'name',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('description'),
accessor: 'description',
Cell: TextOverviewTooltipCell,
className: 'description',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(entries, 'quantity', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(entries, 'rate', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(entries, 'amount', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
],
[],
);
};
/**
* Credit note more actions mneu.

View File

@@ -1,47 +1,74 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormatNumberCell } from '../../../components';
import { FormatNumberCell, TextOverviewTooltipCell } from '../../../components';
import { getColumnWidth } from 'utils';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
/**
* Retrieve table columns of estimate readonly entries details.
*/
export const useEstimateReadonlyEntriesColumns = () =>
React.useMemo(() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
width: 150,
className: 'name',
disableSortBy: true,
},
{
Header: intl.get('description'),
accessor: 'description',
className: 'description',
disableSortBy: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true,
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true,
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true,
},
], []);
export const useEstimateReadonlyEntriesColumns = () => {
// estimate details drawer context.
const {
estimate: { entries },
} = useEstimateDetailDrawerContext();
return React.useMemo(
() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
Cell: TextOverviewTooltipCell,
width: 150,
className: 'name',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('description'),
accessor: 'description',
Cell: TextOverviewTooltipCell,
className: 'description',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'quantity', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'rate', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'amount', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
],
[],
);
};

View File

@@ -1,33 +1,46 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormatNumberCell } from '../../../components';
import { FormatNumberCell, TextOverviewTooltipCell } from '../../../components';
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
import { getColumnWidth } from 'utils';
/**
* Retrieve expense readonly details entries table columns.
*/
export const useExpenseReadEntriesColumns = () =>
React.useMemo(
export const useExpenseReadEntriesColumns = () => {
// Expense drawer context.
const {
expense: { categories },
} = useExpenseDrawerContext();
return React.useMemo(
() => [
{
Header: intl.get('expense_account'),
accessor: 'expense_account.name',
Cell: TextOverviewTooltipCell,
width: 110,
disableSortBy: true,
textOverview: true,
className: 'account',
},
{
Header: intl.get('description'),
accessor: 'description',
width: 110,
disableSortBy: true,
Cell: TextOverviewTooltipCell,
className: 'description',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(categories, 'amount', {
minWidth: 60,
magicSpacing: 5,
}),
disableSortBy: true,
className: 'amount',
align: 'right',
@@ -35,3 +48,4 @@ export const useExpenseReadEntriesColumns = () =>
],
[],
);
};

View File

@@ -1,37 +1,60 @@
import React from 'react';
import intl from 'react-intl-universal';
import { getColumnWidth } from 'utils';
import { TextOverviewTooltipCell } from 'components';
import { useInventoryAdjustmentDrawerContext } from './InventoryAdjustmentDrawerProvider';
export const useInventoryAdjustmentEntriesColumns = () =>
React.useMemo(
export const useInventoryAdjustmentEntriesColumns = () => {
// Inventory adjustment details drawer context.
const {
inventoryAdjustment: { entries },
} = useInventoryAdjustmentDrawerContext();
return React.useMemo(
() => [
{
Header: intl.get('inventory_adjustment.column.product'),
accessor: 'item.name',
width: 150,
Cell: TextOverviewTooltipCell,
width: 100,
className: 'name',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
width: 100,
width: getColumnWidth(entries, 'quantity', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('cost'),
accessor: 'cost',
width: 100,
width: getColumnWidth(entries, 'cost', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('value'),
accessor: 'value',
width: 100,
width: getColumnWidth(entries, 'value', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
],
[],
);
};

View File

@@ -11,12 +11,14 @@ import {
Intent,
Tag,
} from '@blueprintjs/core';
import { getColumnWidth } from 'utils';
import {
FormatNumberCell,
Icon,
FormattedMessage as T,
Choose,
Can,
TextOverviewTooltipCell,
} from 'components';
import {
SaleInvoiceAction,
@@ -27,49 +29,69 @@ import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
/**
* Retrieve invoice readonly details table columns.
*/
export const useInvoiceReadonlyEntriesColumns = () =>
React.useMemo(
export const useInvoiceReadonlyEntriesColumns = () => {
// Invoice details drawer context.
const {
invoice: { entries },
} = useInvoiceDetailDrawerContext();
return React.useMemo(
() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
width: 150,
className: 'name',
Cell: TextOverviewTooltipCell,
disableSortBy: true,
textOverview: true,
width: 150,
},
{
Header: intl.get('description'),
accessor: 'description',
className: 'description',
Cell: TextOverviewTooltipCell,
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true,
textOverview: true,
width: getColumnWidth(entries, 'quantity', {
minWidth: 60,
magicSpacing: 5,
}),
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true,
textOverview: true,
width: getColumnWidth(entries, 'rate', {
minWidth: 60,
magicSpacing: 5,
}),
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true,
textOverview: true,
width: getColumnWidth(entries, 'amount', {
minWidth: 60,
magicSpacing: 5,
}),
},
],
[],
);
};
/**
* Invoice details more actions menu.

View File

@@ -10,7 +10,10 @@ import { TableStyle } from '../../../common';
* Manual journal drawer table.
*/
export default function ManualJournalDrawerTable() {
// Retrieves the readonly manual journal entries columns.
const columns = useManualJournalEntriesColumns();
// Manual journal drawer context.
const { manualJournal } = useManualJournalDrawerContext();
return (

View File

@@ -1,28 +1,17 @@
import intl from 'react-intl-universal';
import React from 'react';
import { Tag, Intent, Classes, Tooltip, Position } from '@blueprintjs/core';
import { Tag, Intent } from '@blueprintjs/core';
import { T, Choose, FormatNumberCell, If, Icon } from '../../../components';
import {
T,
Choose,
FormatNumberCell,
TextOverviewTooltipCell,
} from '../../../components';
import { Features } from 'common';
import { getColumnWidth } from 'utils';
import { useFeatureCan } from 'hooks/state';
/**
* Note column accessor.
*/
export function NoteAccessor(row) {
return (
<If condition={row.note}>
<Tooltip
className={Classes.TOOLTIP_INDICATOR}
content={row.note}
position={Position.LEFT_TOP}
hoverOpenDelay={50}
>
<Icon icon={'file-alt'} iconSize={16} />
</Tooltip>
</If>
);
}
import { useManualJournalDrawerContext } from './ManualJournalDrawerProvider';
/**
* Publish column accessor.
@@ -50,37 +39,45 @@ export function ManualJournalDetailsStatus({ manualJournal }) {
*/
export const useManualJournalEntriesColumns = () => {
const { featureCan } = useFeatureCan();
// manual journal details drawer context.
const {
manualJournal: { entries },
} = useManualJournalDrawerContext();
return React.useMemo(
() => [
{
Header: intl.get('account_name'),
Cell: TextOverviewTooltipCell,
accessor: 'account.name',
width: 130,
disableSortBy: true,
className: 'account',
textOverview: true,
},
{
Header: intl.get('contact'),
accessor: 'contact.display_name',
width: 130,
Cell: TextOverviewTooltipCell,
width: 100,
disableSortBy: true,
className: 'contact',
textOverview: true,
},
{
Header: intl.get('note'),
accessor: NoteAccessor,
width: 80,
accessor: 'note',
Cell: TextOverviewTooltipCell,
disableSortBy: true,
className: 'note',
textOverview: true,
width: 100,
},
...(featureCan(Features.Branches)
? [
{
Header: intl.get('branch'),
width: 130,
width: 100,
accessor: 'branch.name',
disableSortBy: true,
className: 'branch',
},
]
: []),
@@ -88,25 +85,31 @@ export const useManualJournalEntriesColumns = () => {
Header: intl.get('credit'),
accessor: 'credit',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(entries, 'credit', {
minWidth: 60,
magicSpacing: 5,
}),
disableResizable: true,
disableSortBy: true,
textOverview: true,
formatNumber: { noZero: true },
className: 'credit',
align: 'right',
},
{
Header: intl.get('debit'),
accessor: 'debit',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(entries, 'debit', {
minWidth: 60,
magicSpacing: 5,
}),
disableResizable: true,
textOverview: true,
disableSortBy: true,
formatNumber: { noZero: true },
className: 'debit',
align: 'right',
},
],
[featureCan],
[],
);
};

View File

@@ -3,43 +3,65 @@ import intl from 'react-intl-universal';
import moment from 'moment';
import { FormatNumberCell } from '../../../components';
import { getColumnWidth } from 'utils';
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
export const usePaymentMadeEntriesColumns = () =>
React.useMemo(() => [
{
Header: intl.get('date'),
accessor: (row) => moment(row.date).format('YYYY MMM DD'),
width: 100,
disableSortBy: true,
className: 'date',
},
{
Header: intl.get('bill_number'),
accessor: 'bill_no',
width: 150,
disableSortBy: true,
className: 'bill_number',
},
{
Header: intl.get('bill_amount'),
accessor: 'bill.amount',
Cell: FormatNumberCell,
align: 'right',
},
{
Header: intl.get('due_amount'),
accessor: 'bill.due_amount',
Cell: FormatNumberCell,
width: 100,
disableSortBy: true,
align: 'right',
},
{
Header: intl.get('payment_amount'),
accessor: 'payment_amount',
Cell: FormatNumberCell,
width: 100,
disableSortBy: true,
align: 'right',
},
], []);
export const usePaymentMadeEntriesColumns = () => {
// Payment made details context.
const {
paymentMade: { entries },
} = usePaymentMadeDetailContext();
return React.useMemo(
() => [
{
Header: intl.get('date'),
accessor: (row) => moment(row.date).format('YYYY MMM DD'),
width: 100,
disableSortBy: true,
className: 'date',
},
{
Header: intl.get('bill_number'),
accessor: 'bill_no',
width: 150,
disableSortBy: true,
className: 'bill_number',
},
{
Header: intl.get('bill_amount'),
accessor: 'bill.amount',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'bill.amount', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
},
{
Header: intl.get('due_amount'),
accessor: 'bill.due_amount',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'bill.due_amount', {
minWidth: 60,
magicSpacing: 5,
}),
disableSortBy: true,
align: 'right',
},
{
Header: intl.get('payment_amount'),
accessor: 'payment_amount',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'payment_amount', {
minWidth: 60,
magicSpacing: 5,
}),
disableSortBy: true,
textOverview: true,
align: 'right',
},
],
[],
);
};

View File

@@ -2,12 +2,18 @@ import React from 'react';
import intl from 'react-intl-universal';
import moment from 'moment';
import { FormatNumberCell } from '../../../components';
import { getColumnWidth } from 'utils';
import { usePaymentReceiveDetailContext } from './PaymentReceiveDetailProvider';
/**
* Retrieve payment entries table columns.
*/
export const usePaymentReceiveEntriesColumns = () =>
React.useMemo(
export const usePaymentReceiveEntriesColumns = () => {
const {
paymentReceive: { entries },
} = usePaymentReceiveDetailContext();
return React.useMemo(
() => [
{
Header: intl.get('date'),
@@ -27,24 +33,38 @@ export const usePaymentReceiveEntriesColumns = () =>
Header: intl.get('invoice_amount'),
accessor: 'invoice.balance',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'invoice.balance', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
textOverview: true,
},
{
Header: intl.get('amount_due'),
accessor: 'invoice.due_amount',
Cell: FormatNumberCell,
align: 'right',
width: 100,
width: getColumnWidth(entries, 'invoice.due_amount', {
minWidth: 60,
magicSpacing: 5,
}),
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('payment_amount'),
accessor: 'invoice.payment_amount',
Cell: FormatNumberCell,
align: 'right',
width: 100,
width: getColumnWidth(entries, 'invoice.payment_amount', {
minWidth: 60,
magicSpacing: 5,
}),
disableSortBy: true,
textOverview: true,
},
],
[],
);
};

View File

@@ -1,44 +1,69 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormatNumberCell } from '../../../components';
import { getColumnWidth } from 'utils';
import { FormatNumberCell, TextOverviewTooltipCell } from '../../../components';
import { useReceiptDetailDrawerContext } from './ReceiptDetailDrawerProvider.js';
export const useReceiptReadonlyEntriesTableColumns = () => React.useMemo(() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
width: 150,
className: 'name',
disableSortBy: true
},
{
Header: intl.get('description'),
accessor: 'description',
className: 'description',
disableSortBy: true
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true
},
], []);
export const useReceiptReadonlyEntriesTableColumns = () => {
// Receipt details drawer context.
const {
receipt: { entries },
} = useReceiptDetailDrawerContext();
return React.useMemo(
() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
Cell: TextOverviewTooltipCell,
width: 150,
className: 'name',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('description'),
accessor: 'description',
Cell: TextOverviewTooltipCell,
className: 'description',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'quantity', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'rate', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'amount', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
],
[],
);
};

View File

@@ -10,59 +10,82 @@ import {
Tag,
Intent,
} from '@blueprintjs/core';
import { getColumnWidth } from 'utils';
import {
Icon,
FormattedMessage as T,
TextOverviewTooltipCell,
FormatNumberCell,
Choose,
} from '../../../components';
import { useVendorCreditDetailDrawerContext } from './VendorCreditDetailDrawerProvider';
/**
* Retrieve vendor credit readonly details entries table columns.
*/
export const useVendorCreditReadonlyEntriesTableColumns = () =>
React.useMemo(
export const useVendorCreditReadonlyEntriesTableColumns = () => {
const {
vendorCredit: { entries },
} = useVendorCreditDetailDrawerContext();
return React.useMemo(
() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
Cell: TextOverviewTooltipCell,
width: 150,
className: 'item',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('description'),
accessor: 'description',
Cell: TextOverviewTooltipCell,
className: 'description',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(entries, 'quantity', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(entries, 'rate', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(entries, 'amount', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
],
[],
);
};
/**
* Vendor note more actions menu.

View File

@@ -1,40 +1,58 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, Tag } from '@blueprintjs/core';
import { getColumnWidth } from 'utils';
import { useWarehouseDetailDrawerContext } from './WarehouseTransferDetailDrawerProvider';
import {
FormattedMessage as T,
FormatNumberCell,
TextOverviewTooltipCell,
Choose,
} from '../../../components';
export const useWarehouseTransferReadOnlyEntriesColumns = () =>
React.useMemo(
/**
* Retrieves the readonly warehouse transfer entries columns.
*/
export const useWarehouseTransferReadOnlyEntriesColumns = () => {
const {
warehouseTransfer: { entries },
} = useWarehouseDetailDrawerContext();
return React.useMemo(
() => [
{
Header: intl.get('warehouse_transfer.column.item_name'),
accessor: 'item.name',
width: 150,
Cell: TextOverviewTooltipCell,
width: 100,
className: 'name',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('warehouse_transfer.column.description'),
accessor: 'description',
Cell: TextOverviewTooltipCell,
className: 'description',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('warehouse_transfer.column.transfer_quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: 100,
width: getColumnWidth(entries, 'quantity', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
},
],
[],
);
};
/**
* Warehouses transfer details status.

View File

@@ -4,7 +4,7 @@ import { FastField, Field } from 'formik';
import { FormGroup, Checkbox } from '@blueprintjs/core';
import styled from 'styled-components';
import { Row, Col, FieldHint, FormattedMessage as T } from 'components';
import { Flex, FlexItem, FieldHint, FormattedMessage as T } from 'components';
import {
handlePreviousYearCheckBoxChange,
handlePreviousYearChangeCheckboxChange,
@@ -15,11 +15,11 @@ import {
} from './utils';
/**
* Balance sheet header - Comparison panal.
* Balance sheet header - Comparison panal - Comparisons fields.
*/
export default function BalanceSheetHeaderComparisonPanal() {
function BalanceSheetHeaderComparisonPanalFields() {
return (
<BalanceSheetComparisonWrap>
<>
{/**----------- Previous Year -----------*/}
<Field name={'previousYear'} type={'checkbox'}>
{({ form, field }) => (
@@ -33,8 +33,9 @@ export default function BalanceSheetHeaderComparisonPanal() {
</FormGroup>
)}
</Field>
<Row>
<Col xs={3}>
<FlexSubFields align={'left'}>
<FlexItem col={6}>
<Field name={'previousYearAmountChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
@@ -48,8 +49,9 @@ export default function BalanceSheetHeaderComparisonPanal() {
</FormGroup>
)}
</Field>
</Col>
<Col xs={3}>
</FlexItem>
<FlexItem col={6}>
<FastField name={'previousYearPercentageChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
@@ -62,8 +64,9 @@ export default function BalanceSheetHeaderComparisonPanal() {
</FormGroup>
)}
</FastField>
</Col>
</Row>
</FlexItem>
</FlexSubFields>
{/*------------ Previous Period -----------*/}
<FastField name={'previousPeriod'} type={'checkbox'}>
{({ form, field }) => (
@@ -78,8 +81,9 @@ export default function BalanceSheetHeaderComparisonPanal() {
</FormGroup>
)}
</FastField>
<Row>
<Col xs={3}>
<FlexSubFields>
<FlexItem col={6}>
<FastField name={'previousPeriodAmountChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
@@ -93,8 +97,9 @@ export default function BalanceSheetHeaderComparisonPanal() {
</FormGroup>
)}
</FastField>
</Col>
<Col xs={3}>
</FlexItem>
<FlexItem col={6}>
<FastField name={'previousPeriodPercentageChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
@@ -107,8 +112,8 @@ export default function BalanceSheetHeaderComparisonPanal() {
</FormGroup>
)}
</FastField>
</Col>
</Row>
</FlexItem>
</FlexSubFields>
{/**----------- % of Column -----------*/}
<FastField name={'percentageOfColumn'} type={'checkbox'}>
@@ -137,19 +142,33 @@ export default function BalanceSheetHeaderComparisonPanal() {
</FormGroup>
)}
</FastField>
</>
);
}
/**
* Balance sheet header - Comparison panal.
*/
export default function BalanceSheetHeaderComparisonPanal() {
return (
<BalanceSheetComparisonWrap>
<BalanceSheetComparisonFieldsWrap>
<BalanceSheetHeaderComparisonPanalFields />
</BalanceSheetComparisonFieldsWrap>
</BalanceSheetComparisonWrap>
);
}
const BalanceSheetComparisonWrap = styled.div`
.row {
margin-left: 0.15rem;
.col {
min-width: 150px !important;
max-width: 190px !important;
}
}
.bp3-form-group {
margin-bottom: 3px;
}
`;
const FlexSubFields = styled(Flex)`
padding-left: 20px;
`;
const BalanceSheetComparisonFieldsWrap = styled.div`
width: 400px;
`;

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { FormGroup, Classes } from '@blueprintjs/core';
import { Classes } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import classNames from 'classnames';
import { AccountMultiSelect, Row, Col } from 'components';
import { FFormGroup } from '../../../components/Forms';
@@ -47,10 +46,10 @@ function GLHeaderGeneralPaneContent() {
<Col xs={4}>
<FFormGroup
label={<T id={'specific_accounts'} />}
name={'accounts'}
name={'accountsIds'}
className={Classes.FILL}
>
<AccountMultiSelect name="accounts" accounts={accounts} />
<AccountMultiSelect name="accountsIds" accounts={accounts} />
</FFormGroup>
</Col>
</Row>

View File

@@ -32,7 +32,7 @@ export const getDefaultGeneralLedgerQuery = () => {
basis: 'accural',
filterByOption: 'with-transactions',
branchesIds: [],
accounts: [],
accountsIds: [],
};
};
@@ -49,8 +49,9 @@ const parseGeneralLedgerQuery = (locationQuery) => {
return {
...transformed,
// Ensures the branches ids is always array.
// Ensures the branches, accounts ids is always array.
branchesIds: castArray(transformed.branchesIds),
accountsIds: castArray(transformed.accountsIds),
};
};

View File

@@ -1,11 +1,11 @@
import React from 'react';
import { FastField, Field } from 'formik';
import { FastField } from 'formik';
import { FormGroup, Checkbox } from '@blueprintjs/core';
import styled from 'styled-components';
import { FormattedMessage as T } from 'components';
import { Flex, FlexItem, FormattedMessage as T } from 'components';
import { Row, Col, FieldHint } from '../../../components';
import { FieldHint } from '../../../components';
import {
handlePreviousYearCheckBoxChange,
handlePreviousPeriodCheckBoxChange,
@@ -16,11 +16,12 @@ import {
} from './utils';
/**
* ProfitLoss sheet header -comparison panel.
* Profit/loss comparisons panel fields.
* @returns {JSX.Element}
*/
export default function ProfitLossSheetHeaderComparisonPanel() {
function ProfitLossComaprsionPanelFields() {
return (
<ProfitLossSheetComparisonWrap>
<>
{/**----------- Previous Year -----------*/}
<FastField name={'previousYear'} type={'checkbox'}>
{({ form, field }) => (
@@ -35,8 +36,9 @@ export default function ProfitLossSheetHeaderComparisonPanel() {
</FormGroup>
)}
</FastField>
<Row>
<Col xs={3}>
<FlexSubFields>
<FlexItem col={6}>
<FastField name={'previousYearAmountChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
@@ -50,8 +52,8 @@ export default function ProfitLossSheetHeaderComparisonPanel() {
</FormGroup>
)}
</FastField>
</Col>
<Col xs={3}>
</FlexItem>
<FlexItem col={6}>
<FastField name={'previousYearPercentageChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
@@ -65,8 +67,9 @@ export default function ProfitLossSheetHeaderComparisonPanel() {
</FormGroup>
)}
</FastField>
</Col>
</Row>
</FlexItem>
</FlexSubFields>
{/**----------- Previous Period (PP) -----------*/}
<FastField name={'previousPeriod'} type={'checkbox'}>
{({ form, field }) => (
@@ -81,8 +84,9 @@ export default function ProfitLossSheetHeaderComparisonPanel() {
</FormGroup>
)}
</FastField>
<Row>
<Col xs={3}>
<FlexSubFields>
<FlexItem col={6}>
<FastField name={'previousPeriodAmountChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
@@ -96,8 +100,8 @@ export default function ProfitLossSheetHeaderComparisonPanel() {
</FormGroup>
)}
</FastField>
</Col>
<Col xs={3}>
</FlexItem>
<FlexItem col={6}>
<FastField name={'previousPeriodPercentageChange'} type={'checkbox'}>
{({ form, field }) => (
<FormGroup labelInfo={<FieldHint />}>
@@ -111,8 +115,9 @@ export default function ProfitLossSheetHeaderComparisonPanel() {
</FormGroup>
)}
</FastField>
</Col>
</Row>
</FlexItem>
</FlexSubFields>
{/**----------- % of Column -----------*/}
<FastField name={'percentageColumn'} type={'checkbox'}>
{({ field }) => (
@@ -126,6 +131,7 @@ export default function ProfitLossSheetHeaderComparisonPanel() {
</FormGroup>
)}
</FastField>
{/**----------- % of Row -----------*/}
<FastField name={'percentageRow'} type={'checkbox'}>
{({ field }) => (
@@ -139,6 +145,7 @@ export default function ProfitLossSheetHeaderComparisonPanel() {
</FormGroup>
)}
</FastField>
{/**----------- % of Expense -----------*/}
<FastField name={'percentageExpense'} type={'checkbox'}>
{({ field }) => (
@@ -152,6 +159,7 @@ export default function ProfitLossSheetHeaderComparisonPanel() {
</FormGroup>
)}
</FastField>
{/**----------- % of Income -----------*/}
<FastField name={'percentageIncome'} type={'checkbox'}>
{({ field }) => (
@@ -165,19 +173,33 @@ export default function ProfitLossSheetHeaderComparisonPanel() {
</FormGroup>
)}
</FastField>
</>
);
}
/**
* ProfitLoss sheet header -comparison panel.
*/
export default function ProfitLossSheetHeaderComparisonPanel() {
return (
<ProfitLossSheetComparisonWrap>
<ProfitLossComaprsionFieldsWrap>
<ProfitLossComaprsionPanelFields />
</ProfitLossComaprsionFieldsWrap>
</ProfitLossSheetComparisonWrap>
);
}
const ProfitLossSheetComparisonWrap = styled.div`
.row {
margin-left: 0.15rem;
.col {
min-width: 150px !important;
max-width: 190px !important;
}
}
.bp3-form-group {
margin-bottom: 3px;
}
`;
const FlexSubFields = styled(Flex)`
padding-left: 20px;
`;
const ProfitLossComaprsionFieldsWrap = styled.div`
max-width: 400px;
`;

View File

@@ -12,7 +12,6 @@ function VendorsBalanceSummaryProvider({ filter, ...props }) {
const query = React.useMemo(() => transformFilterFormToQuery(filter), [
filter,
]);
// Fetching vendors balance summary report based on the given query.
const {
data: VendorBalanceSummary,

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { MenuItem, Button } from '@blueprintjs/core';
import { FSelect } from '../../../components';
/**
*
* @param {*}
* @param {*} param1
* @returns
*/
const chargeTypeItemRenderer = (item, { handleClick, modifiers, query }) => {
return (
<MenuItem
label={item.label}
key={item.name}
onClick={handleClick}
text={item.name}
/>
);
};
const chargeTypeSelectProps = {
itemRenderer: chargeTypeItemRenderer,
valueAccessor: 'value',
labelAccessor: 'name',
};
/**
*
* @param param0
* @returns
*/
export function ChangeTypesSelect({ items, ...rest }) {
return (
<FSelect
{...chargeTypeSelectProps}
{...rest}
items={items}
input={ChargeTypeSelectButton}
/>
);
}
/**
*
* @param param0
* @returns
*/
function ChargeTypeSelectButton({ label }) {
return <Button text={label} />;
}

View File

@@ -0,0 +1,67 @@
import React from 'react';
import intl from 'react-intl-universal';
import { MenuItem, Button } from '@blueprintjs/core';
import { FSelect } from 'components';
/**
*
* @param query
* @param expense
* @param _index
* @param exactMatch
*/
const expenseItemPredicate = (query, expense, _index, exactMatch) => {
const normalizedTitle = expense.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return `${expense.name}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
}
};
/**
*
* @param expense
* @param param1
* @returns
*/
const expenseItemRenderer = (expense, { handleClick, modifiers, query }) => {
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
key={expense.id}
onClick={handleClick}
text={expense.name}
/>
);
};
const expenseSelectProps = {
itemPredicate: expenseItemPredicate,
itemRenderer: expenseItemRenderer,
valueAccessor: 'id',
labelAccessor: 'name',
};
export function ExpenseSelect({ expenses, defaultText, ...rest }) {
return (
<FSelect
items={expenses}
{...expenseSelectProps}
{...rest}
input={ExpenseSelectButton}
/>
);
}
function ExpenseSelectButton({ label, ...rest }) {
return (
<Button
text={label ? label : intl.get('choose_an_estimated_expense')}
{...rest}
/>
);
}

View File

@@ -0,0 +1,20 @@
//@ts-nocheck
import React from 'react';
import { FInputGroup } from 'components';
import { useFormikContext } from 'formik';
export function FInputGroupComponent({ toField, ...props }) {
const { values, setFieldValue } = useFormikContext();
const { expenseQuantity, expenseUnitPrice } = values;
const total = expenseQuantity * expenseUnitPrice;
const handleBlur = () => {
setFieldValue(toField, total);
};
const inputGroupProps = {
onBlur: handleBlur,
...props,
};
return <FInputGroup {...inputGroupProps} />;
}

View File

@@ -0,0 +1,63 @@
import React from 'react';
import intl from 'react-intl-universal';
import { MenuItem, Button } from '@blueprintjs/core';
import { FSelect } from 'components';
/**
*
* @param {*} query
* @param {*} project
* @param {*} _index
* @param {*} exactMatch
* @returns
*/
const projectsItemPredicate = (query, project, _index, exactMatch) => {
const normalizedTitle = project.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return `${project.name}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
}
};
/**
*
* @param {*} project
* @param {*} param1
* @returns
*/
const projectsItemRenderer = (project, { handleClick, modifiers, query }) => {
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
key={project.id}
onClick={handleClick}
text={project.name}
/>
);
};
const projectSelectProps = {
itemPredicate: projectsItemPredicate,
itemRenderer: projectsItemRenderer,
valueAccessor: 'id',
labelAccessor: 'name',
};
export function ProjectsSelect({ projects, ...rest }) {
return (
<FSelect
items={projects}
{...projectSelectProps}
{...rest}
input={ProjectSelectButton}
/>
);
}
function ProjectSelectButton({ label }) {
return <Button text={label ? label : intl.get('find_or_choose_a_project')} />;
}

View File

@@ -0,0 +1,64 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { MenuItem, Button } from '@blueprintjs/core';
import { FSelect } from 'components';
/**
*
* @param {*} query
* @param {*} task
* @param {*} _index
* @param {*} exactMatch
* @returns
*/
const taskItemPredicate = (query, task, _index, exactMatch) => {
const normalizedTitle = task.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return `${task.name}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
}
};
/**
*
* @param {*} task
* @param {*} param1
* @returns
*/
const taskItemRenderer = (task, { handleClick, modifiers, query }) => {
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
key={task.id}
onClick={handleClick}
text={task.name}
/>
);
};
const taskSelectProps = {
itemPredicate: taskItemPredicate,
itemRenderer: taskItemRenderer,
valueAccessor: 'id',
labelAccessor: 'name',
};
export function TaskSelect({ tasks, ...rest }) {
return (
<FSelect
items={tasks}
{...taskSelectProps}
{...rest}
input={TaskSelectButton}
/>
);
}
function TaskSelectButton({ label }) {
return <Button text={label ? label : intl.get('choose_a_task')} />;
}

View File

@@ -0,0 +1,5 @@
export * from './ExpenseSelect';
export * from './ChangeTypesSelect';
export * from './TaskSelect';
export * from './ProjectsSelect';
export * from './FInputGroupComponent';

View File

@@ -0,0 +1,19 @@
import * as Yup from 'yup';
import intl from 'react-intl-universal';
import { DATATYPES_LENGTH } from 'common/dataTypes';
const Schema = Yup.object().shape({
estimatedExpense: Yup.number().label(
intl.get('estimated_expense.schema.label.estimated_expense'),
),
quantity: Yup.number().label(
intl.get('estimated_expense.schema.label.quantity'),
),
unitPrice: Yup.number().label(
intl.get('estimated_expense.schema.label.unit_price'),
),
expenseTotal: Yup.number(),
charge: Yup.string(),
});
export const CreateEstimatedExpenseFormSchema = Schema;

View File

@@ -0,0 +1,56 @@
import React from 'react';
import { Formik } from 'formik';
import { AppToaster } from 'components';
import { CreateEstimatedExpenseFormSchema } from './EstimatedExpense.schema';
import EstimatedExpenseFormConent from './EstimatedExpenseFormConent';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
const defaultInitialValues = {
estimatedExpense: '',
unitPrice: '',
quantity: 1,
charge: '% markup',
percentage: '',
};
/**
* Estimated expense form dialog.
* @returns
*/
function EstimatedExpenseForm({
//#withDialogActions
closeDialog,
}) {
const initialValues = {
...defaultInitialValues,
};
// Handles the form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
// Handle request response success.
const onSuccess = (response) => {
AppToaster.show({});
};
// Handle request response errors.
const onError = ({
response: {
data: { errors },
},
}) => {
setSubmitting(false);
};
};
return (
<Formik
validationSchema={CreateEstimatedExpenseFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
component={EstimatedExpenseFormConent}
/>
);
}
export default compose(withDialogActions)(EstimatedExpenseForm);

View File

@@ -0,0 +1,54 @@
//@ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Classes, ControlGroup } from '@blueprintjs/core';
import { FFormGroup, FInputGroup, Choose } from 'components';
import { useFormikContext } from 'formik';
function PercentageFormField() {
return (
<FFormGroup
label={intl.get('estimated_expenses.dialog.percentage')}
name={'percentage'}
>
<FInputGroup name="percentage" />
</FFormGroup>
);
}
function CustomPirceField() {
return (
<ControlGroup className={Classes.FILL}>
<FFormGroup
name={'unitPrice'}
label={intl.get('estimated_expenses.dialog.unit_price')}
>
<FInputGroup name="unitPrice" />
</FFormGroup>
<FFormGroup
name={'unitPrice'}
label={intl.get('estimated_expenses.dialog.total')}
>
<FInputGroup name="total" />
</FFormGroup>
</ControlGroup>
);
}
/**
* estimate expense form charge fields.
* @returns
*/
export default function EstimatedExpenseFormChargeFields() {
const { values } = useFormikContext();
return (
<Choose>
<Choose.When condition={values.charge === 'markup'}>
<PercentageFormField />
</Choose.When>
<Choose.When condition={values.charge === 'custom_pirce'}>
<CustomPirceField />
</Choose.When>
</Choose>
);
}

View File

@@ -0,0 +1,17 @@
import React from 'react';
import { Form } from 'formik';
import EstimatedExpenseFormFields from './EstimatedExpenseFormFields';
import EstimatedExpenseFormFloatingActions from './EstimatedExpenseFormFloatingActions';
/**
* Estimated expense form content.
* @returns
*/
export default function EstimatedExpenseFormConent() {
return (
<Form>
<EstimatedExpenseFormFields />
<EstimatedExpenseFormFloatingActions />
</Form>
);
}

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { EstimatedExpenseFormProvider } from './EstimatedExpenseFormProvider';
import EstimatedExpenseForm from './EstimatedExpenseForm';
/**
* Estimate expense form dialog.
* @return
*/
export default function EstimatedExpenseFormDialogContent({
//#ownProps
dialogName,
estimatedExpense,
}) {
return (
<EstimatedExpenseFormProvider
dialogName={dialogName}
estimatedExpenseId={estimatedExpense}
>
<EstimatedExpenseForm />
</EstimatedExpenseFormProvider>
);
}

View File

@@ -0,0 +1,115 @@
//@ts-nocheck
import React from 'react';
import styled from 'styled-components';
import intl from 'react-intl-universal';
import { Classes, ControlGroup } from '@blueprintjs/core';
import classNames from 'classnames';
import {
FFormGroup,
FInputGroup,
FormattedMessage as T,
FieldRequiredHint,
} from 'components';
import { ExpenseSelect, FInputGroupComponent } from '../../components';
import { useEstimatedExpenseFormContext } from './EstimatedExpenseFormProvider';
import EstimatedExpenseFormChargeFields from './EstimatedExpenseFormChargeFields';
import { ChangeTypesSelect } from '../../components';
import { expenseChargeOption } from 'containers/Projects/containers/common/modalChargeOptions';
/**
* Estimated expense form fields.
* @returns
*/
export default function EstimatedExpenseFormFields() {
return (
<div className={Classes.DIALOG_BODY}>
{/*------------ Estimated Expense -----------*/}
<FFormGroup
name={'estimatedExpense'}
label={intl.get('estimated_expenses.dialog.estimated_expense')}
className={classNames('form-group--select-list', Classes.FILL)}
>
<ExpenseSelect
name={'estimatedExpense'}
popoverProps={{ minimal: true }}
expenses={[]}
/>
</FFormGroup>
{/*------------ Quantity -----------*/}
<FFormGroup
label={intl.get('estimated_expenses.dialog.quantity')}
name={'quantity'}
>
<FInputGroupComponent name="quantity" />
</FFormGroup>
<MetaLineLabel>
<T id={'estimated_expenses.dialog.cost_to_you'} />
</MetaLineLabel>
{/*------------ Unit Price -----------*/}
<ControlGroup className={Classes.FILL}>
<FFormGroup
name={'unitPrice'}
label={intl.get('estimated_expenses.dialog.unit_price')}
>
<FInputGroupComponent name="unitPrice" />
</FFormGroup>
<FFormGroup
name={'unitPrice'}
label={intl.get('estimated_expenses.dialog.total')}
>
<FInputGroup name="expenseTotal" />
</FFormGroup>
</ControlGroup>
<MetaLineLabel>
<T id={'estimated_expenses.dialog.what_you_ll_charge'} />
</MetaLineLabel>
{/*------------ Charge -----------*/}
<FFormGroup
name={'charge'}
label={<T id={'estimated_expenses.dialog.charge'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<ChangeTypesSelect
name="charge"
items={expenseChargeOption}
popoverProps={{ minimal: true }}
filterable={false}
/>
</FFormGroup>
<EstimatedExpenseFormChargeFields />
{/*------------ Estimated Amount -----------*/}
<EstimatedAmountWrap>
<EstimatedAmountLabel>
<T id={'estimated_expenses.dialog.estimated_amount'} />
</EstimatedAmountLabel>
<EstimatedAmount>0.00</EstimatedAmount>
</EstimatedAmountWrap>
</div>
);
}
const MetaLineLabel = styled.div`
font-size: 14px;
line-height: 1.5rem;
font-weight: 500;
margin-bottom: 8px;
`;
const EstimatedAmountWrap = styled.div`
display: block;
text-align: right;
`;
const EstimatedAmountLabel = styled.span`
font-size: 14px;
line-height: 1.5rem;
opacity: 0.75;
`;
const EstimatedAmount = styled.span`
font-size: 15px;
font-weight: 700;
padding-left: 14px;
line-height: 2rem;
`;

View File

@@ -0,0 +1,48 @@
//@ts-nocheck
import React from 'react';
import { useFormikContext } from 'formik';
import { Intent, Button, Classes } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import { useEstimatedExpenseFormContext } from './EstimatedExpenseFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
/**
* Estimated expense form floating actions.
* @returns
*/
function EstimatedExpenseFormFloatingActions({
// #withDialogActions
closeDialog,
}) {
// Formik context.
const { isSubmitting } = useFormikContext();
// expense form dialog context.
const { dialogName } = useEstimatedExpenseFormContext();
// Handle close button click.
const handleCancelBtnClick = () => {
closeDialog(dialogName);
};
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleCancelBtnClick} style={{ minWidth: '75px' }}>
<T id={'cancel'} />
</Button>
<Button
intent={Intent.PRIMARY}
loading={isSubmitting}
style={{ minWidth: '75px' }}
type="submit"
>
{<T id={'save'} />}
</Button>
</div>
</div>
);
}
export default compose(withDialogActions)(EstimatedExpenseFormFloatingActions);

View File

@@ -0,0 +1,31 @@
//@ts-nocheck
import React from 'react';
import { DialogContent } from 'components';
const EstimatedExpenseFormContext = React.createContext();
/**
* Estimated expense form provider.
* @returns
*/
function EstimatedExpenseFormProvider({
//#OwnProps
dialogName,
estimatedExpenseId,
...props
}) {
// state provider.
const provider = {
dialogName,
};
return (
<DialogContent>
<EstimatedExpenseFormContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useEstimatedExpenseFormContext = () =>
React.useContext(EstimatedExpenseFormContext);
export { EstimatedExpenseFormProvider, useEstimatedExpenseFormContext };

View File

@@ -0,0 +1,55 @@
import React from 'react';
import styled from 'styled-components';
import { Dialog, DialogSuspense, FormattedMessage as T } from 'components';
import withDialogRedux from 'components/DialogReduxConnect';
import { compose } from 'utils';
const EstimatedExpenseFormDialogContent = React.lazy(
() => import('./EstimatedExpenseFormDialogContent'),
);
/**
* Estimate expense form dialog.
* @returns
*/
function EstimatedExpenseFormDialog({
dialogName,
payload: { projectId = null },
isOpen,
}) {
return (
<EstimateExpenseFormDialogRoot
name={dialogName}
title={<T id={'estimated_expenses.dialog.label'} />}
isOpen={isOpen}
autoFocus={true}
canEscapeKeyClose={true}
style={{ width: '400px' }}
>
<DialogSuspense>
<EstimatedExpenseFormDialogContent
dialogName={dialogName}
estimatedExpense={projectId}
/>
</DialogSuspense>
</EstimateExpenseFormDialogRoot>
);
}
export default compose(withDialogRedux())(EstimatedExpenseFormDialog);
const EstimateExpenseFormDialogRoot = styled(Dialog)`
.bp3-dialog-body {
.bp3-form-group {
margin-bottom: 15px;
label.bp3-label {
margin-bottom: 3px;
font-size: 13px;
}
}
}
.bp3-dialog-footer {
padding-top: 10px;
}
`;

View File

@@ -0,0 +1,79 @@
//@ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useDeleteProject } from '../../hooks';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* Project delete alert.
*/
function ProjectDeleteAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { projectId },
// #withAlertActions
closeAlert,
// #withDrawerActions
closeDrawer,
}) {
const { mutateAsync: deleteProjectMutate, isLoading } = useDeleteProject();
// handle cancel delete project alert.
const handleCancelDeleteAlert = () => {
closeAlert(name);
};
// handleConfirm delete project
const handleConfirmProjectDelete = () => {
deleteProjectMutate(projectId)
.then(() => {
AppToaster.show({
message: intl.get('projects.alert.delete_message'),
intent: Intent.SUCCESS,
});
})
.catch(
({
response: {
data: { errors },
},
}) => {},
)
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={isOpen}
onCancel={handleCancelDeleteAlert}
onConfirm={handleConfirmProjectDelete}
loading={isLoading}
>
<p>
<FormattedHTMLMessage id={'projects.alert.once_delete_this_project'} />
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(ProjectDeleteAlert);

View File

@@ -0,0 +1,8 @@
import React from 'react';
const ProjectDeleteAlert = React.lazy(() => import('./ProjectDeleteAlert'));
/**
* Project alerts.
*/
export default [{ name: 'project-delete', component: ProjectDeleteAlert }];

View File

@@ -0,0 +1,131 @@
// @ts-nocheck
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Button,
Classes,
NavbarDivider,
NavbarGroup,
Alignment,
} from '@blueprintjs/core';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import {
Icon,
FormattedMessage as T,
DashboardRowsHeightButton,
} from 'components';
import { ProjectTransactionsSelect } from './components';
import withSettings from '../../../Settings/withSettings';
import withSettingsActions from '../../../Settings/withSettingsActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { projectTranslations } from './common';
import { useProjectDetailContext } from './ProjectDetailProvider';
import { compose } from 'utils';
/**
* Project detail actions bar.
* @returns
*/
function ProjectDetailActionsBar({
// #withDialogActions
openDialog,
// #withSettings
timesheetsTableSize,
// #withSettingsActions
addSetting,
}) {
const history = useHistory();
const { projectId } = useProjectDetailContext();
// Handle new transaction button click.
const handleNewTransactionBtnClick = ({ path }) => {
switch (path) {
case 'expense':
openDialog('project-expense-form', { projectId });
break;
case 'estimated_expense':
openDialog('estimated-expense-form', { projectId });
}
};
const handleEditProjectBtnClick = () => {
openDialog('project-form', {
projectId,
});
};
// Handle table row size change.
const handleTableRowSizeChange = (size) => {
addSetting('timesheets', 'tableSize', size) &&
addSetting('sales', 'tableSize', size) &&
addSetting('purchases', 'tableSize', size);
};
const handleTimeEntryBtnClick = () => {
openDialog('project-time-entry-form', {
projectId,
});
};
// Handle the refresh button click.
const handleRefreshBtnClick = () => {};
return (
<DashboardActionsBar>
<NavbarGroup>
<ProjectTransactionsSelect
transactions={projectTranslations}
onItemSelect={handleNewTransactionBtnClick}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'time-24'} iconSize={16} />}
text={<T id={'projcet_details.action.time_entry'} />}
onClick={handleTimeEntryBtnClick}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'projcet_details.action.edit_project'} />}
onClick={handleEditProjectBtnClick}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'print-16'} iconSize={'16'} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'file-import-16'} />}
text={<T id={'import'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
text={<T id={'export'} />}
/>
<NavbarDivider />
<DashboardRowsHeightButton
initialValue={timesheetsTableSize}
onChange={handleTableRowSizeChange}
/>
</NavbarGroup>
<NavbarGroup align={Alignment.RIGHT}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="refresh-16" iconSize={14} />}
onClick={handleRefreshBtnClick}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withDialogActions,
withSettingsActions,
withSettings(({ timesheetsSettings }) => ({
timesheetsTableSize: timesheetsSettings?.tableSize,
})),
)(ProjectDetailActionsBar);

View File

@@ -0,0 +1,29 @@
// @ts-nocheck
import React from 'react';
import DashboardInsider from '../../../../components/Dashboard/DashboardInsider';
const ProjectDetailContext = React.createContext();
/**
* Project detail provider.
* @returns
*/
function ProjectDetailProvider({
projectId,
// #ownProps
...props
}) {
// State provider.
const provider = {
projectId,
};
return (
<DashboardInsider class="timesheets">
<ProjectDetailContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useProjectDetailContext = () => React.useContext(ProjectDetailContext);
export { ProjectDetailProvider, useProjectDetailContext };

View File

@@ -0,0 +1,79 @@
import React from 'react';
import styled from 'styled-components';
import intl from 'react-intl-universal';
import { Tabs, Tab } from '@blueprintjs/core';
import ProjectTimeSheets from './ProjectTimeSheets';
import ProjectPurchasesTable from './ProjectPurchasesTable';
import ProjectSalesTable from './ProjectSalesTable';
/**
* Project detail tabs.
* @returns
*/
export default function ProjectDetailTabs() {
return (
<ProjectTabsContent>
<Tabs
animate={true}
large={true}
renderActiveTabPanelOnly={true}
defaultSelectedTabId={'purchases'}
>
<Tab id="overview" title={intl.get('project_details.label.overview')} />
<Tab
id="timesheet"
title={intl.get('project_details.label.timesheet')}
panel={<ProjectTimeSheets />}
/>
<Tab
id="purchases"
title={intl.get('project_details.label.purchases')}
panel={<ProjectPurchasesTable />}
/>
<Tab
id="sales"
title={intl.get('project_details.label.sales')}
panel={<ProjectSalesTable />}
/>
<Tab id="journals" title={intl.get('project_details.label.journals')} />
</Tabs>
</ProjectTabsContent>
);
}
const ProjectTabsContent = styled.div`
.bp3-tabs {
.bp3-tab-list {
padding: 0 20px;
background-color: #fff;
border-bottom: 1px solid #d2dce2;
> *:not(:last-child) {
margin-right: 0;
}
&.bp3-large > .bp3-tab {
font-size: 15px;
font-weight: 400;
color: #7f8596;
margin: 0 0.9rem;
&[aria-selected='true'],
&:not([aria-disabled='true']):hover {
color: #0052cc;
}
}
.bp3-tab-indicator-wrapper .bp3-tab-indicator {
height: 2px;
bottom: -2px;
}
}
.bp3-tab-panel {
/* margin: 20px 32px; */
/* margin: 20px; */
/* margin-top: 20px;
margin-bottom: 20px;
padding: 0 25px; */
}
}
`;

View File

@@ -0,0 +1,57 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import { DataTable } from 'components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import { TABLES } from 'common/tables';
import { useMemorizedColumnsWidths } from 'hooks';
import { ActionMenu } from './components';
import { useProjectPurchasesColumns } from './hooks';
import withSettings from '../../../../Settings/withSettings';
import { compose } from 'utils';
/**
* Project Purchases DataTable.
* @returns
*/
function ProjectPurchasesTableRoot({
// #withSettings
purchasesTableSize,
}) {
// Retrieve purchases table columns.
const columns = useProjectPurchasesColumns();
// Handle delete purchase.
const handleDeletePurchase = () => {};
// Local storage memorizing columns widths.
const [initialColumnsWidths, , handleColumnResizing] =
useMemorizedColumnsWidths(TABLES.PURCHASES);
return (
<DataTable
columns={columns}
data={[]}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
ContextMenu={ActionMenu}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing}
size={purchasesTableSize}
payload={{
onDelete: handleDeletePurchase,
}}
/>
);
}
export const ProjectPurchasesTable = compose(
withSettings(({ purchasesSettings }) => ({
purchasesTableSize: purchasesSettings?.tableSize,
})),
)(ProjectPurchasesTableRoot);

View File

@@ -0,0 +1,21 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Icon } from 'components';
import { Menu, MenuItem, Intent } from '@blueprintjs/core';
import { safeCallback } from 'utils';
/**
* Table actions cell.
*/
export function ActionMenu({ payload: { onDelete }, row: { original } }) {
return (
<Menu>
<MenuItem
text={intl.get('purchases.action.delete')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
);
}

View File

@@ -0,0 +1,73 @@
import { useMemo } from 'react';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import { CLASSES } from 'common/classes';
import { FormatDateCell } from 'components';
export function useProjectPurchasesColumns() {
return useMemo(
() => [
{
id: 'date',
Header: intl.get('purchases.column.date'),
accessor: 'date',
Cell: FormatDateCell,
width: 120,
className: 'date',
clickable: true,
textOverview: true,
},
{
id: 'type',
Header: intl.get('purchases.column.type'),
accessor: 'type',
width: 120,
className: 'type',
clickable: true,
textOverview: true,
},
{
id: 'transaction_no',
Header: intl.get('purchases.column.transaction_no'),
accessor: 'transaction_no',
width: 120,
},
{
id: 'due_date',
Header: intl.get('purchases.column.due_date'),
accessor: 'due_date',
Cell: FormatDateCell,
width: 120,
className: 'due_date',
clickable: true,
textOverview: true,
},
{
id: 'balance',
Header: intl.get('purchases.column.balance'),
accessor: 'balance',
width: 120,
clickable: true,
align: 'right',
className: clsx(CLASSES.FONT_BOLD),
},
{
id: 'total',
Header: intl.get('purchases.column.total'),
accessor: 'total',
align: 'right',
width: 120,
className: clsx(CLASSES.FONT_BOLD),
},
{
id: 'status',
Header: intl.get('purchases.column.status'),
accessor: 'status',
width: 120,
className: 'status',
clickable: true,
},
],
[],
);
}

View File

@@ -0,0 +1,16 @@
import React from 'react';
import styled from 'styled-components';
import { ProjectPurchasesTable } from './ProjectPurchasesTable';
import { DashboardContentTable } from 'components';
/**
*
* @returns
*/
export default function ProjectPurchasesTableRoot() {
return (
<DashboardContentTable>
<ProjectPurchasesTable />
</DashboardContentTable>
);
}

View File

@@ -0,0 +1,56 @@
//@ts-nocheck
import React from 'react';
import { DataTable } from 'components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import { TABLES } from 'common/tables';
import { useMemorizedColumnsWidths } from 'hooks';
import { ActionMenu } from './components';
import { useProjectSalesColumns } from './hooks';
import withSettings from '../../../../Settings/withSettings';
import { compose } from 'utils';
/**
* Porject sales datatable.
* @returns
*/
function ProjectSalesTableRoot({
// #withSettings
salesTableSize,
}) {
// Retrieve project sales table columns.
const columns = useProjectSalesColumns();
// Handle delete sale.
const handleDeleteSale = () => {};
// Local storage memorizing columns widths.
const [initialColumnsWidths, , handleColumnResizing] =
useMemorizedColumnsWidths(TABLES.SALES);
return (
<DataTable
columns={columns}
data={[]}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
ContextMenu={ActionMenu}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing}
size={salesTableSize}
payload={{
onDelete: handleDeleteSale,
}}
/>
);
}
export const ProjectSalesTable = compose(
withSettings(({ salesSettings }) => ({
salesTableSize: salesSettings?.tableSize,
})),
)(ProjectSalesTableRoot);

View File

@@ -0,0 +1,22 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormatDateCell, Icon, FormattedMessage as T } from 'components';
import { Menu, MenuItem, Intent } from '@blueprintjs/core';
import { safeCallback } from 'utils';
/**
* Table actions cell.
*/
export function ActionMenu({ payload: { onDelete }, row: { original } }) {
return (
<Menu>
<MenuItem
text={intl.get('sales.action.delete')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
);
}

View File

@@ -0,0 +1,73 @@
import { useMemo } from 'react';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import { CLASSES } from 'common/classes';
import { FormatDateCell } from 'components';
export function useProjectSalesColumns() {
return useMemo(
() => [
{
id: 'date',
Header: intl.get('sales.column.date'),
accessor: 'date',
Cell: FormatDateCell,
width: 120,
className: 'date',
clickable: true,
textOverview: true,
},
{
id: 'type',
Header: intl.get('sales.column.type'),
accessor: 'type',
width: 120,
className: 'type',
clickable: true,
textOverview: true,
},
{
id: 'transaction_no',
Header: intl.get('sales.column.transaction_no'),
accessor: 'transaction_no',
width: 120,
},
{
id: 'due_date',
Header: intl.get('sales.column.due_date'),
accessor: 'due_date',
Cell: FormatDateCell,
width: 120,
className: 'due_date',
clickable: true,
textOverview: true,
},
{
id: 'balance',
Header: intl.get('sales.column.balance'),
accessor: 'balance',
width: 120,
clickable: true,
align: 'right',
className: clsx(CLASSES.FONT_BOLD),
},
{
id: 'total',
Header: intl.get('sales.column.total'),
accessor: 'total',
align: 'right',
width: 120,
className: clsx(CLASSES.FONT_BOLD),
},
{
id: 'status',
Header: intl.get('sales.column.status'),
accessor: 'status',
width: 120,
className: 'status',
clickable: true,
},
],
[],
);
}

View File

@@ -0,0 +1,18 @@
import React from 'react';
import styled from 'styled-components';
import { ProjectSalesTable } from './ProjectSalesTable';
import { DashboardContentTable } from 'components';
/**
* Project Sales Table.
* @returns
*/
export default function ProjectSalesTableRoot() {
return (
<ProjectSalesContentTable>
<ProjectSalesTable />
</ProjectSalesContentTable>
);
}
const ProjectSalesContentTable = styled(DashboardContentTable)``;

View File

@@ -0,0 +1,41 @@
//@ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { Intent } from '@blueprintjs/core';
import { FormatDate } from 'components';
import {
DetailFinancialCard,
DetailFinancialSection,
FinancialProgressBar,
FinancialCardText,
} from '../components';
import { calculateStatus } from 'utils';
/**
* Project Timesheets header
* @returns
*/
export function ProjectTimesheetsHeader() {
return (
<DetailFinancialSection>
<DetailFinancialCard label={'Project estimate'} value={'3.14'} />
<DetailFinancialCard label={'Invoiced'} value={'0.00'}>
<FinancialCardText>0% of project estimate</FinancialCardText>
<FinancialProgressBar intent={Intent.NONE} value={0} />
</DetailFinancialCard>
<DetailFinancialCard label={'Time & Expenses'} value={'0.00'}>
<FinancialCardText>0% of project estimate</FinancialCardText>
<FinancialProgressBar intent={Intent.NONE} value={0} />
</DetailFinancialCard>
<DetailFinancialCard label={'To be invoiced'} value={'3.14'} />
<DetailFinancialCard
label={'Deadline'}
value={<FormatDate value={'2022-06-08T22:00:00.000Z'} />}
>
<FinancialCardText>4 days to go</FinancialCardText>
</DetailFinancialCard>
</DetailFinancialSection>
);
}

View File

@@ -0,0 +1,104 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import { DataTable } from 'components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import { ActionsMenu } from './components';
import { useProjectTimesheetColumns } from './hooks';
import { TABLES } from 'common/tables';
import { useMemorizedColumnsWidths } from 'hooks';
import withSettings from '../../../../Settings/withSettings';
import { compose } from 'utils';
/**
* Timesheet DataTable.
* @returns
*/
function ProjectTimesheetsTableRoot({
// #withSettings
timesheetsTableSize,
}) {
// Retrieve project timesheet table columns.
const columns = useProjectTimesheetColumns();
// Handle delete timesheet.
const handleDeleteTimesheet = () => {};
// Local storage memorizing columns widths.
const [initialColumnsWidths, , handleColumnResizing] =
useMemorizedColumnsWidths(TABLES.TIMESHEETS);
return (
<ProjectTimesheetDataTable
columns={columns}
data={[]}
manualSortBy={true}
noInitialFetch={true}
sticky={true}
hideTableHeader={true}
ContextMenu={ActionsMenu}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing}
size={timesheetsTableSize}
payload={{
onDelete: handleDeleteTimesheet,
}}
/>
);
}
export const ProjectTimesheetsTable = compose(
withSettings(({ timesheetsSettings }) => ({
timesheetsTableSize: timesheetsSettings?.tableSize,
})),
)(ProjectTimesheetsTableRoot);
const ProjectTimesheetDataTable = styled(DataTable)`
.table {
.thead .tr .th {
.resizer {
display: none;
}
}
.tbody {
.tr .td {
}
.avatar.td {
.cell-inner {
.avatar {
display: inline-block;
background: #adbcc9;
border-radius: 50%;
text-align: center;
font-weight: 400;
color: #fff;
&[data-size='medium'] {
height: 30px;
width: 30px;
line-height: 30px;
font-size: 14px;
}
&[data-size='small'] {
height: 25px;
width: 25px;
line-height: 25px;
font-size: 12px;
}
}
}
}
}
}
.table-size--small {
.tbody .tr {
height: 45px;
}
}
`;

View File

@@ -0,0 +1,78 @@
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { FormatDate, Icon } from 'components';
import { Menu, MenuItem, Intent } from '@blueprintjs/core';
import { safeCallback, firstLettersArgs } from 'utils';
/**
* Table actions cell.
*/
export function ActionsMenu({
payload: { onDelete, onViewDetails },
row: { original },
}) {
return (
<Menu>
<MenuItem
text={intl.get('timesheets.actions.delete_timesheet')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
);
}
/**
* Avatar cell.
*/
export const AvatarCell = ({ row: { original }, size }) => (
<span className="avatar" data-size={size}>
{firstLettersArgs(original?.display_name, original?.name)}
</span>
);
/**
* Timesheet accessor.
*/
export const TimesheetAccessor = (timesheet) => (
<React.Fragment>
<TimesheetHeader>
<TimesheetTitle>{timesheet.display_name}</TimesheetTitle>
<TimesheetSubTitle>{timesheet.name}</TimesheetSubTitle>
</TimesheetHeader>
<TimesheetContent>
<FormatDate value={timesheet.date} />
<TimesheetDescription>{timesheet.description}</TimesheetDescription>
</TimesheetContent>
</React.Fragment>
);
const TimesheetHeader = styled.div`
display: flex;
align-items: baseline;
flex-flow: wrap;
`;
const TimesheetTitle = styled.span`
font-weight: 500;
margin-right: 12px;
line-height: 1.5rem;
`;
const TimesheetSubTitle = styled.span``;
const TimesheetContent = styled.div`
display: block;
white-space: nowrap;
font-size: 13px;
opacity: 0.75;
margin-bottom: 0.1rem;
line-height: 1.2rem;
`;
const TimesheetDescription = styled.span`
&::before {
content: '•';
margin: 0.3rem;
}
`;

View File

@@ -0,0 +1,43 @@
import React from 'react';
import intl from 'react-intl-universal';
import { AvatarCell, TimesheetAccessor } from './components';
/**
* Retrieve project timesheet list columns.
*/
export function useProjectTimesheetColumns() {
return React.useMemo(
() => [
{
id: 'avatar',
Header: '',
Cell: AvatarCell,
className: 'avatar',
width: 45,
disableResizing: true,
disableSortBy: true,
clickable: true,
},
{
id: 'name',
Header: 'Header',
accessor: TimesheetAccessor,
width: 100,
className: 'name',
clickable: true,
textOverview: true,
},
{
id: 'duration',
Header: '',
accessor: 'duration',
width: 100,
className: 'duration',
align: 'right',
clickable: true,
textOverview: true,
},
],
[],
);
}

View File

@@ -0,0 +1,27 @@
import React from 'react';
import styled from 'styled-components';
import { ProjectTimesheetsTable } from './ProjectTimesheetsTable';
import { ProjectTimesheetsHeader } from './ProjectTimesheetsHeader';
/**
* Project Timesheets.
* @returns
*/
export default function ProjectTimeSheets() {
return (
<React.Fragment>
<ProjectTimesheetsHeader />
<ProjectTimesheetTableCard>
<ProjectTimesheetsTable />
</ProjectTimesheetTableCard>
</React.Fragment>
);
}
const ProjectTimesheetTableCard = styled.div`
margin: 22px 32px;
border: 1px solid #c8cad0;
border-radius: 3px;
background: #fff;
`;

View File

@@ -0,0 +1,9 @@
import intl from 'react-intl-universal';
export const projectTranslations = [
{ name: intl.get('project_details.new_expenses'), path: 'expense' },
{
name: intl.get('project_details.new_estimated_expenses'),
path: 'estimated_expense',
},
];

Some files were not shown because too many files have changed in this diff Show More