mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 15:50:32 +00:00
refactoring: migrating to react-query to manage service-side state.
This commit is contained in:
@@ -77,7 +77,7 @@
|
|||||||
"react-hotkeys-hook": "^3.0.3",
|
"react-hotkeys-hook": "^3.0.3",
|
||||||
"react-intl": "^3.12.0",
|
"react-intl": "^3.12.0",
|
||||||
"react-loadable": "^5.5.0",
|
"react-loadable": "^5.5.0",
|
||||||
"react-query": "^2.4.6",
|
"react-query": "^3.6.0",
|
||||||
"react-redux": "^7.1.3",
|
"react-redux": "^7.1.3",
|
||||||
"react-router-breadcrumbs-hoc": "^3.2.10",
|
"react-router-breadcrumbs-hoc": "^3.2.10",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
|
|||||||
223
client/src/common/accountTypes.js
Normal file
223
client/src/common/accountTypes.js
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
export const ACCOUNT_TYPE = {
|
||||||
|
CASH: 'cash',
|
||||||
|
BANK: 'bank',
|
||||||
|
ACCOUNTS_RECEIVABLE: 'accounts-receivable',
|
||||||
|
INVENTORY: 'inventory',
|
||||||
|
OTHER_CURRENT_ASSET: 'other-ACCOUNT_PARENT_TYPE.CURRENT_ASSET',
|
||||||
|
FIXED_ASSET: 'fixed-asset',
|
||||||
|
NON_CURRENT_ASSET: 'non-ACCOUNT_PARENT_TYPE.CURRENT_ASSET',
|
||||||
|
|
||||||
|
ACCOUNTS_PAYABLE: 'accounts-payable',
|
||||||
|
CREDIT_CARD: 'credit-card',
|
||||||
|
TAX_PAYABLE: 'tax-payable',
|
||||||
|
OTHER_CURRENT_LIABILITY: 'other-current-liability',
|
||||||
|
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||||
|
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||||
|
|
||||||
|
EQUITY: 'equity',
|
||||||
|
INCOME: 'income',
|
||||||
|
OTHER_INCOME: 'other-income',
|
||||||
|
COST_OF_GOODS_SOLD: 'cost-of-goods-sold',
|
||||||
|
EXPENSE: 'expense',
|
||||||
|
OTHER_EXPENSE: 'other-expense',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ACCOUNT_PARENT_TYPE = {
|
||||||
|
CURRENT_ASSET: 'current-asset',
|
||||||
|
FIXED_ASSET: 'fixed-asset',
|
||||||
|
NON_CURRENT_ASSET: 'non-ACCOUNT_PARENT_TYPE.CURRENT_ASSET',
|
||||||
|
|
||||||
|
CURRENT_LIABILITY: 'current-liability',
|
||||||
|
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||||
|
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||||
|
|
||||||
|
EQUITY: 'equity',
|
||||||
|
EXPENSE: 'expense',
|
||||||
|
INCOME: 'income',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ACCOUNT_ROOT_TYPE = {
|
||||||
|
ASSET: 'asset',
|
||||||
|
LIABILITY: 'liability',
|
||||||
|
EQUITY: 'equity',
|
||||||
|
EXPENSE: 'expene',
|
||||||
|
INCOME: 'income',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ACCOUNT_NORMAL = {
|
||||||
|
CREDIT: 'credit',
|
||||||
|
DEBIT: 'debit',
|
||||||
|
};
|
||||||
|
export const ACCOUNT_TYPES = [
|
||||||
|
{
|
||||||
|
label: 'Cash',
|
||||||
|
key: ACCOUNT_TYPE.CASH,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Bank',
|
||||||
|
key: ACCOUNT_TYPE.BANK,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Accounts Receivable',
|
||||||
|
key: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Inventory',
|
||||||
|
key: ACCOUNT_TYPE.INVENTORY,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Current Asset',
|
||||||
|
key: ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Fixed Asset',
|
||||||
|
key: ACCOUNT_TYPE.FIXED_ASSET,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Non-Current Asset',
|
||||||
|
key: ACCOUNT_TYPE.NON_CURRENT_ASSET,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Accounts Payable',
|
||||||
|
key: ACCOUNT_TYPE.ACCOUNTS_PAYABLE,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Credit Card',
|
||||||
|
key: ACCOUNT_TYPE.CREDIT_CARD,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tax Payable',
|
||||||
|
key: ACCOUNT_TYPE.TAX_PAYABLE,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Current Liability',
|
||||||
|
key: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Long Term Liability',
|
||||||
|
key: ACCOUNT_TYPE.LOGN_TERM_LIABILITY,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.LOGN_TERM_LIABILITY,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Non-Current Liability',
|
||||||
|
key: ACCOUNT_TYPE.NON_CURRENT_LIABILITY,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.NON_CURRENT_LIABILITY,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Equity',
|
||||||
|
key: ACCOUNT_TYPE.EQUITY,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.EQUITY,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.EQUITY,
|
||||||
|
balanceSheet: true,
|
||||||
|
incomeSheet: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Income',
|
||||||
|
key: ACCOUNT_TYPE.INCOME,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Income',
|
||||||
|
key: ACCOUNT_TYPE.OTHER_INCOME,
|
||||||
|
normal: ACCOUNT_NORMAL.CREDIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cost of Goods Sold',
|
||||||
|
key: ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Expense',
|
||||||
|
key: ACCOUNT_TYPE.EXPENSE,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Expense',
|
||||||
|
key: ACCOUNT_TYPE.OTHER_EXPENSE,
|
||||||
|
normal: ACCOUNT_NORMAL.DEBIT,
|
||||||
|
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||||
|
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||||
|
balanceSheet: false,
|
||||||
|
incomeSheet: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -2,9 +2,7 @@ import React from 'react';
|
|||||||
import { RawIntlProvider } from 'react-intl';
|
import { RawIntlProvider } from 'react-intl';
|
||||||
import { Router, Switch, Route } from 'react-router';
|
import { Router, Switch, Route } from 'react-router';
|
||||||
import { createBrowserHistory } from 'history';
|
import { createBrowserHistory } from 'history';
|
||||||
import { ReactQueryConfigProvider } from 'react-query';
|
import { QueryClientProvider, QueryClient } from 'react-query';
|
||||||
import { ReactQueryDevtools } from 'react-query-devtools';
|
|
||||||
|
|
||||||
import 'style/App.scss';
|
import 'style/App.scss';
|
||||||
|
|
||||||
import PrivateRoute from 'components/Guards/PrivateRoute';
|
import PrivateRoute from 'components/Guards/PrivateRoute';
|
||||||
@@ -17,14 +15,18 @@ function App({ locale }) {
|
|||||||
const history = createBrowserHistory();
|
const history = createBrowserHistory();
|
||||||
|
|
||||||
const queryConfig = {
|
const queryConfig = {
|
||||||
queries: {
|
defaultOptions: {
|
||||||
refetchOnWindowFocus: false,
|
queries: {
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const queryClient = new QueryClient(queryConfig);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RawIntlProvider value={intl}>
|
<RawIntlProvider value={intl}>
|
||||||
<div className="App">
|
<QueryClientProvider client={queryClient}>
|
||||||
<ReactQueryConfigProvider config={queryConfig}>
|
<div className="App">
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={'/auth'}>
|
<Route path={'/auth'}>
|
||||||
@@ -38,9 +40,8 @@ function App({ locale }) {
|
|||||||
</Router>
|
</Router>
|
||||||
|
|
||||||
<GlobalErrors />
|
<GlobalErrors />
|
||||||
<ReactQueryDevtools />
|
</div>
|
||||||
</ReactQueryConfigProvider>
|
</QueryClientProvider>
|
||||||
</div>
|
|
||||||
</RawIntlProvider>
|
</RawIntlProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import classNames from 'classnames';
|
|||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
export default function CategoriesSelectList({
|
export default function CategoriesSelectList({
|
||||||
categoriesList,
|
categories,
|
||||||
selecetedCategoryId,
|
selecetedCategoryId,
|
||||||
defaultSelectText = <T id={'select_category'} />,
|
defaultSelectText = <T id={'select_category'} />,
|
||||||
onCategorySelected,
|
onCategorySelected,
|
||||||
@@ -41,7 +41,7 @@ export default function CategoriesSelectList({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ListSelect
|
<ListSelect
|
||||||
items={categoriesList}
|
items={categories}
|
||||||
selectedItemProp={'id'}
|
selectedItemProp={'id'}
|
||||||
selectedItem={selecetedCategoryId}
|
selectedItem={selecetedCategoryId}
|
||||||
textProp={'name'}
|
textProp={'name'}
|
||||||
|
|||||||
131
client/src/components/ContextMenu.tsx
Normal file
131
client/src/components/ContextMenu.tsx
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Palantir Technologies, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// import classNames from 'classnames';
|
||||||
|
import * as React from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
// import { polyfill } from "react-lifecycles-compat";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
Classes,
|
||||||
|
Position,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
|
||||||
|
|
||||||
|
// import { IOverlayLifecycleProps } from "../overlay/overlay";
|
||||||
|
// import { Popover } from "../popover/popover";
|
||||||
|
// import { PopperModifiers } from "../popover/popoverSharedProps";
|
||||||
|
|
||||||
|
export interface IOffset {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IContextMenuState {
|
||||||
|
isOpen: boolean;
|
||||||
|
isDarkTheme: boolean;
|
||||||
|
menu?: JSX.Element;
|
||||||
|
offset?: IOffset;
|
||||||
|
onClose?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const POPPER_MODIFIERS = {
|
||||||
|
preventOverflow: { boundariesElement: "viewport" },
|
||||||
|
};
|
||||||
|
const TRANSITION_DURATION = 100;
|
||||||
|
|
||||||
|
// type IContextMenuProps = IOverlayLifecycleProps;
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
|
||||||
|
export default class ContextMenu extends React.PureComponent {
|
||||||
|
public state: IContextMenuState = {
|
||||||
|
isDarkTheme: false,
|
||||||
|
isOpen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
// prevent right-clicking in a context menu
|
||||||
|
const content = <div onContextMenu={this.cancelContextMenu}>{this.state.menu}</div>;
|
||||||
|
const popoverClassName = {};
|
||||||
|
|
||||||
|
// HACKHACK: workaround until we have access to Popper#scheduleUpdate().
|
||||||
|
// https://github.com/palantir/blueprint/issues/692
|
||||||
|
// Generate key based on offset so a new Popover instance is created
|
||||||
|
// when offset changes, to force recomputing position.
|
||||||
|
const key = this.state.offset === undefined ? "" : `${this.state.offset.left}x${this.state.offset.top}`;
|
||||||
|
|
||||||
|
// wrap the popover in a positioned div to make sure it is properly
|
||||||
|
// offset on the screen.
|
||||||
|
return (
|
||||||
|
<div className={Classes.CONTEXT_MENU_POPOVER_TARGET} style={this.state.offset}>
|
||||||
|
<Popover
|
||||||
|
{...this.props}
|
||||||
|
backdropProps={{ onContextMenu: this.handleBackdropContextMenu }}
|
||||||
|
content={content}
|
||||||
|
enforceFocus={false}
|
||||||
|
key={key}
|
||||||
|
hasBackdrop={true}
|
||||||
|
isOpen={this.state.isOpen}
|
||||||
|
minimal={true}
|
||||||
|
// modifiers={POPPER_MODIFIERS}
|
||||||
|
onInteraction={this.handlePopoverInteraction}
|
||||||
|
position={Position.RIGHT_TOP}
|
||||||
|
// popoverClassName={popoverClassName}
|
||||||
|
target={<div />}
|
||||||
|
transitionDuration={TRANSITION_DURATION}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public show(menu: JSX.Element, offset: IOffset, onClose?: () => void, isDarkTheme = false) {
|
||||||
|
this.setState({ isOpen: true, menu, offset, onClose, isDarkTheme });
|
||||||
|
}
|
||||||
|
|
||||||
|
public hide() {
|
||||||
|
this.state.onClose?.();
|
||||||
|
this.setState({ isOpen: false, onClose: undefined });
|
||||||
|
}
|
||||||
|
|
||||||
|
private cancelContextMenu = (e: React.SyntheticEvent<HTMLDivElement>) => e.preventDefault();
|
||||||
|
|
||||||
|
private handleBackdropContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
// React function to remove from the event pool, useful when using a event within a callback
|
||||||
|
e.persist();
|
||||||
|
e.preventDefault();
|
||||||
|
// wait for backdrop to disappear so we can find the "real" element at event coordinates.
|
||||||
|
// timeout duration is equivalent to transition duration so we know it's animated out.
|
||||||
|
setTimeout(() => {
|
||||||
|
// retrigger context menu event at the element beneath the backdrop.
|
||||||
|
// if it has a `contextmenu` event handler then it'll be invoked.
|
||||||
|
// if it doesn't, no native menu will show (at least on OSX) :(
|
||||||
|
const newTarget = document.elementFromPoint(e.clientX, e.clientY);
|
||||||
|
const { view, ...newEventInit } = e;
|
||||||
|
newTarget?.dispatchEvent(new MouseEvent("contextmenu", newEventInit));
|
||||||
|
}, TRANSITION_DURATION);
|
||||||
|
};
|
||||||
|
|
||||||
|
private handlePopoverInteraction = (nextOpenState: boolean) => {
|
||||||
|
if (!nextOpenState) {
|
||||||
|
// delay the actual hiding till the event queue clears
|
||||||
|
// to avoid flicker of opening twice
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -5,9 +5,11 @@ import { Button, Tabs, Tab, Tooltip, Position } from '@blueprintjs/core';
|
|||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { If, Icon } from 'components';
|
import { If, Icon } from 'components';
|
||||||
|
import { saveInvoke } from 'utils';
|
||||||
|
|
||||||
export default function DashboardViewsTabs({
|
export default function DashboardViewsTabs({
|
||||||
initialViewId = 0,
|
initialViewId = 0,
|
||||||
|
viewId,
|
||||||
tabs,
|
tabs,
|
||||||
defaultTabText = <T id={'all'} />,
|
defaultTabText = <T id={'all'} />,
|
||||||
allTab = true,
|
allTab = true,
|
||||||
@@ -26,16 +28,16 @@ export default function DashboardViewsTabs({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleTabClick = (viewId) => {
|
const handleTabClick = (viewId) => {
|
||||||
onTabClick && onTabClick(viewId);
|
saveInvoke(onTabClick, viewId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mappedTabs = useMemo(
|
const mappedTabs = useMemo(
|
||||||
() => tabs.map((tab) => ({ ...tab, onTabClick: handleTabClick })),
|
() => tabs.map((tab) => ({ ...tab, onTabClick: handleTabClick })),
|
||||||
[tabs],
|
[tabs, handleTabClick],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleViewLinkClick = () => {
|
const handleViewLinkClick = () => {
|
||||||
onNewViewTabClick && onNewViewTabClick();
|
saveInvoke(onNewViewTabClick);
|
||||||
};
|
};
|
||||||
|
|
||||||
const debounceChangeHistory = useRef(
|
const debounceChangeHistory = useRef(
|
||||||
@@ -49,7 +51,7 @@ export default function DashboardViewsTabs({
|
|||||||
debounceChangeHistory.current(`/${resourceName}/${toPath}`);
|
debounceChangeHistory.current(`/${resourceName}/${toPath}`);
|
||||||
|
|
||||||
setCurrentView(viewId);
|
setCurrentView(viewId);
|
||||||
onChange && onChange(viewId);
|
saveInvoke(onChange, viewId);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -26,15 +26,14 @@ function DashboardPrivatePages({
|
|||||||
// #withSubscriptionsActions
|
// #withSubscriptionsActions
|
||||||
requestFetchSubscriptions,
|
requestFetchSubscriptions,
|
||||||
}) {
|
}) {
|
||||||
// Fetch all user's organizatins.
|
// Fetches all user's organizatins.
|
||||||
const fetchOrganizations = useQuery(
|
const fetchOrganizations = useQuery(
|
||||||
['organizations'], () => requestAllOrganizations(),
|
['organizations'], () => requestAllOrganizations(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fetchs organization subscriptions.
|
// Fetches organization subscriptions.
|
||||||
const fetchSuscriptions = useQuery(
|
const fetchSuscriptions = useQuery(
|
||||||
['susbcriptions'], () => requestFetchSubscriptions(),
|
['susbcriptions'], () => requestFetchSubscriptions(),
|
||||||
{ enabled: fetchOrganizations.data },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ export default function DataTable(props) {
|
|||||||
minWidth: selectionColumnWidth,
|
minWidth: selectionColumnWidth,
|
||||||
width: selectionColumnWidth,
|
width: selectionColumnWidth,
|
||||||
maxWidth: selectionColumnWidth,
|
maxWidth: selectionColumnWidth,
|
||||||
|
skeletonWidthMin: 100,
|
||||||
// The header can use the table's getToggleAllRowsSelectedProps method
|
// The header can use the table's getToggleAllRowsSelectedProps method
|
||||||
// to render a checkbox
|
// to render a checkbox
|
||||||
Header: TableIndeterminateCheckboxHeader,
|
Header: TableIndeterminateCheckboxHeader,
|
||||||
@@ -198,4 +199,7 @@ DataTable.defaultProps = {
|
|||||||
TableTBodyRenderer: TableTBody,
|
TableTBodyRenderer: TableTBody,
|
||||||
TablePaginationRenderer: TablePagination,
|
TablePaginationRenderer: TablePagination,
|
||||||
TableNoResultsRowRenderer: TableNoResultsRow,
|
TableNoResultsRowRenderer: TableNoResultsRow,
|
||||||
|
|
||||||
|
noResults: 'There is no results in the table.',
|
||||||
|
payload: {},
|
||||||
};
|
};
|
||||||
@@ -27,11 +27,13 @@ function TableHeaderCell({ column, index }) {
|
|||||||
</span>
|
</span>
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
<div {...column.getSortByToggleProps({
|
<div
|
||||||
className: classNames('cell-inner', {
|
{...column.getSortByToggleProps({
|
||||||
'text-overview': column.textOverview,
|
className: classNames('cell-inner', {
|
||||||
})
|
'text-overview': column.textOverview,
|
||||||
})}>
|
}),
|
||||||
|
})}
|
||||||
|
>
|
||||||
{column.render('Header')}
|
{column.render('Header')}
|
||||||
|
|
||||||
<If condition={column.isSorted}>
|
<If condition={column.isSorted}>
|
||||||
@@ -74,9 +76,13 @@ function TableHeaderGroup({ headerGroup }) {
|
|||||||
*/
|
*/
|
||||||
export default function TableHeader() {
|
export default function TableHeader() {
|
||||||
const {
|
const {
|
||||||
table: { headerGroups },
|
table: { headerGroups, page },
|
||||||
|
props: { TableHeaderSkeletonRenderer, headerLoading },
|
||||||
} = useContext(TableContext);
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
if (headerLoading && TableHeaderSkeletonRenderer) {
|
||||||
|
return <TableHeaderSkeletonRenderer />;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<ScrollSyncPane>
|
<ScrollSyncPane>
|
||||||
<div className="thead">
|
<div className="thead">
|
||||||
|
|||||||
42
client/src/components/Datatable/TableHeaderSkeleton.js
Normal file
42
client/src/components/Datatable/TableHeaderSkeleton.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import TableContext from './TableContext';
|
||||||
|
import { Skeleton } from 'components';
|
||||||
|
|
||||||
|
function TableHeaderCell({ column }) {
|
||||||
|
const { skeletonWidthMax = 100, skeletonWidthMin = 40 } = column;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...column.getHeaderProps({
|
||||||
|
className: 'th',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table skeleton rows.
|
||||||
|
*/
|
||||||
|
export default function TableSkeletonHeader({}) {
|
||||||
|
const {
|
||||||
|
table: { headerGroups },
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="thead">
|
||||||
|
{headerGroups.map((headerGroup) => (
|
||||||
|
<div
|
||||||
|
{...headerGroup.getHeaderGroupProps({
|
||||||
|
className: 'tr',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{headerGroup.headers.map((column) => (
|
||||||
|
<TableHeaderCell column={column} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ export default function TableRows() {
|
|||||||
table: { prepareRow, page },
|
table: { prepareRow, page },
|
||||||
props: { TableRowRenderer, TableCellRenderer },
|
props: { TableRowRenderer, TableCellRenderer },
|
||||||
} = useContext(TableContext);
|
} = useContext(TableContext);
|
||||||
|
|
||||||
return page.map((row) => {
|
return page.map((row) => {
|
||||||
prepareRow(row);
|
prepareRow(row);
|
||||||
return <TableRowRenderer row={row} TableCellRenderer={TableCellRenderer} />;
|
return <TableRowRenderer row={row} TableCellRenderer={TableCellRenderer} />;
|
||||||
|
|||||||
44
client/src/components/Datatable/TableSkeletonRows.js
Normal file
44
client/src/components/Datatable/TableSkeletonRows.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import TableContext from './TableContext';
|
||||||
|
import { Skeleton } from 'components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table header cell.
|
||||||
|
*/
|
||||||
|
function TableHeaderCell({ column }) {
|
||||||
|
const { skeletonWidthMax = 100, skeletonWidthMin = 40 } = column;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...column.getHeaderProps({
|
||||||
|
className: 'td',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table skeleton rows.
|
||||||
|
*/
|
||||||
|
export default function TableSkeletonRows({}) {
|
||||||
|
const {
|
||||||
|
table: { headerGroups },
|
||||||
|
} = useContext(TableContext);
|
||||||
|
const skeletonRows = 10;
|
||||||
|
|
||||||
|
return Array.from({ length: skeletonRows }).map(() => {
|
||||||
|
return headerGroups.map((headerGroup) => (
|
||||||
|
<div
|
||||||
|
{...headerGroup.getHeaderGroupProps({
|
||||||
|
className: 'tr',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{headerGroup.headers.map((column) => (
|
||||||
|
<TableHeaderCell column={column} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -11,9 +11,11 @@ import EstimateNumberDialog from 'containers/Dialogs/EstimateNumberDialog';
|
|||||||
import ReceiptNumberDialog from 'containers/Dialogs/ReceiptNumberDialog';
|
import ReceiptNumberDialog from 'containers/Dialogs/ReceiptNumberDialog';
|
||||||
import InvoiceNumberDialog from 'containers/Dialogs/InvoiceNumberDialog';
|
import InvoiceNumberDialog from 'containers/Dialogs/InvoiceNumberDialog';
|
||||||
import InventoryAdjustmentDialog from 'containers/Dialogs/InventoryAdjustmentFormDialog';
|
import InventoryAdjustmentDialog from 'containers/Dialogs/InventoryAdjustmentFormDialog';
|
||||||
|
|
||||||
import PaymentViaVoucherDialog from 'containers/Dialogs/PaymentViaVoucherDialog';
|
import PaymentViaVoucherDialog from 'containers/Dialogs/PaymentViaVoucherDialog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialogs container.
|
||||||
|
*/
|
||||||
export default function DialogsContainer() {
|
export default function DialogsContainer() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -27,7 +29,7 @@ export default function DialogsContainer() {
|
|||||||
<InviteUserDialog dialogName={'invite-user'} />
|
<InviteUserDialog dialogName={'invite-user'} />
|
||||||
<ExchangeRateFormDialog dialogName={'exchangeRate-form'} />
|
<ExchangeRateFormDialog dialogName={'exchangeRate-form'} />
|
||||||
<ItemCategoryDialog dialogName={'item-category-form'} />
|
<ItemCategoryDialog dialogName={'item-category-form'} />
|
||||||
<InventoryAdjustmentDialog dialogName={'inventory-adjustment-form'} />
|
<InventoryAdjustmentDialog dialogName={'inventory-adjustment'} />
|
||||||
<PaymentViaVoucherDialog dialogName={'payment-via-voucher'} />
|
<PaymentViaVoucherDialog dialogName={'payment-via-voucher'} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
19
client/src/components/Skeleton.js
Normal file
19
client/src/components/Skeleton.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import 'style/components/Skeleton.scss';
|
||||||
|
|
||||||
|
import { randomNumber } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skeleton component.
|
||||||
|
*/
|
||||||
|
export default function Skeleton({
|
||||||
|
Tag = 'span',
|
||||||
|
minWidth = 40,
|
||||||
|
maxWidth = 100,
|
||||||
|
}) {
|
||||||
|
const randomWidth = useMemo(() => randomNumber(minWidth, maxWidth), [
|
||||||
|
minWidth,
|
||||||
|
maxWidth,
|
||||||
|
]);
|
||||||
|
return <Tag className={'skeleton'} style={{ width: `${randomWidth}%` }} />;
|
||||||
|
}
|
||||||
@@ -44,7 +44,7 @@ import InputPrependText from './Forms/InputPrependText';
|
|||||||
import PageFormBigNumber from './PageFormBigNumber';
|
import PageFormBigNumber from './PageFormBigNumber';
|
||||||
import AccountsMultiSelect from './AccountsMultiSelect';
|
import AccountsMultiSelect from './AccountsMultiSelect';
|
||||||
import CustomersMultiSelect from './CustomersMultiSelect';
|
import CustomersMultiSelect from './CustomersMultiSelect';
|
||||||
|
import Skeleton from './Skeleton'
|
||||||
|
|
||||||
import TableFastCell from './Datatable/TableFastCell';
|
import TableFastCell from './Datatable/TableFastCell';
|
||||||
|
|
||||||
@@ -97,6 +97,6 @@ export {
|
|||||||
AccountsMultiSelect,
|
AccountsMultiSelect,
|
||||||
DataTableEditable,
|
DataTableEditable,
|
||||||
CustomersMultiSelect,
|
CustomersMultiSelect,
|
||||||
|
|
||||||
TableFastCell,
|
TableFastCell,
|
||||||
|
Skeleton,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -171,7 +171,8 @@ export default [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
text: <T id={'system'} />,
|
||||||
|
label: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'preferences'} />,
|
text: <T id={'preferences'} />,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { orderingLinesIndexes, repeatValue } from 'utils';
|
|||||||
|
|
||||||
export default function MakeJournalEntriesField({
|
export default function MakeJournalEntriesField({
|
||||||
defaultRow,
|
defaultRow,
|
||||||
|
|
||||||
linesNumber = 4,
|
linesNumber = 4,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -18,23 +18,20 @@ import MakeJournalEntriesField from './MakeJournalEntriesField';
|
|||||||
import MakeJournalNumberWatcher from './MakeJournalNumberWatcher';
|
import MakeJournalNumberWatcher from './MakeJournalNumberWatcher';
|
||||||
import MakeJournalFormFooter from './MakeJournalFormFooter';
|
import MakeJournalFormFooter from './MakeJournalFormFooter';
|
||||||
|
|
||||||
import withJournalsActions from 'containers/Accounting/withJournalsActions';
|
|
||||||
import withManualJournalDetail from 'containers/Accounting/withManualJournalDetail';
|
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
|
||||||
import AppToaster from 'components/AppToaster';
|
import AppToaster from 'components/AppToaster';
|
||||||
import Dragzone from 'components/Dragzone';
|
|
||||||
import withMediaActions from 'containers/Media/withMediaActions';
|
import withMediaActions from 'containers/Media/withMediaActions';
|
||||||
import {
|
import {
|
||||||
compose,
|
compose,
|
||||||
repeatValue,
|
repeatValue,
|
||||||
orderingLinesIndexes,
|
orderingLinesIndexes,
|
||||||
defaultToTransform,
|
defaultToTransform,
|
||||||
|
transactionNumber,
|
||||||
} from 'utils';
|
} from 'utils';
|
||||||
import { transformErrors } from './utils';
|
import { transformErrors } from './utils';
|
||||||
import withManualJournalsActions from './withManualJournalsActions';
|
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||||
|
|
||||||
const defaultEntry = {
|
const defaultEntry = {
|
||||||
index: 0,
|
index: 0,
|
||||||
@@ -59,18 +56,6 @@ const defaultInitialValues = {
|
|||||||
* Journal entries form.
|
* Journal entries form.
|
||||||
*/
|
*/
|
||||||
function MakeJournalEntriesForm({
|
function MakeJournalEntriesForm({
|
||||||
// #withMedia
|
|
||||||
requestSubmitMedia,
|
|
||||||
requestDeleteMedia,
|
|
||||||
|
|
||||||
// #withJournalsActions
|
|
||||||
requestMakeJournalEntries,
|
|
||||||
requestEditManualJournal,
|
|
||||||
setJournalNumberChanged,
|
|
||||||
|
|
||||||
// #withManualJournals
|
|
||||||
journalNumberChanged,
|
|
||||||
|
|
||||||
// #withDashboard
|
// #withDashboard
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
changePageSubtitle,
|
changePageSubtitle,
|
||||||
@@ -79,30 +64,33 @@ function MakeJournalEntriesForm({
|
|||||||
journalNextNumber,
|
journalNextNumber,
|
||||||
journalNumberPrefix,
|
journalNumberPrefix,
|
||||||
baseCurrency,
|
baseCurrency,
|
||||||
// #ownProps
|
|
||||||
manualJournalId,
|
|
||||||
manualJournal,
|
|
||||||
onFormSubmit,
|
|
||||||
onCancelForm,
|
|
||||||
}) {
|
}) {
|
||||||
const isNewMode = !manualJournalId;
|
const {
|
||||||
const [submitPayload, setSubmitPayload] = useState({});
|
createJournalMutate,
|
||||||
|
editJournalMutate,
|
||||||
|
isNewMode,
|
||||||
|
manualJournal,
|
||||||
|
submitPayload,
|
||||||
|
} = useMakeJournalFormContext();
|
||||||
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const journalNumber = isNewMode
|
// New journal number.
|
||||||
? `${journalNumberPrefix}-${journalNextNumber}`
|
const journalNumber = transactionNumber(
|
||||||
: journalNextNumber;
|
journalNumberPrefix,
|
||||||
|
journalNextNumber,
|
||||||
|
);
|
||||||
|
// Changes the page title based on the form in new and edit mode.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const transactionNumber = manualJournal
|
const transactionNumber = manualJournal
|
||||||
? manualJournal.journal_number
|
? manualJournal.journal_number
|
||||||
: journalNumber;
|
: journalNumber;
|
||||||
|
|
||||||
if (manualJournal && manualJournal.id) {
|
if (isNewMode) {
|
||||||
changePageTitle(formatMessage({ id: 'edit_journal' }));
|
|
||||||
} else {
|
|
||||||
changePageTitle(formatMessage({ id: 'new_journal' }));
|
changePageTitle(formatMessage({ id: 'new_journal' }));
|
||||||
|
} else {
|
||||||
|
changePageTitle(formatMessage({ id: 'edit_journal' }));
|
||||||
}
|
}
|
||||||
changePageSubtitle(
|
changePageSubtitle(
|
||||||
defaultToTransform(transactionNumber, `No. ${transactionNumber}`, ''),
|
defaultToTransform(transactionNumber, `No. ${transactionNumber}`, ''),
|
||||||
@@ -113,6 +101,7 @@ function MakeJournalEntriesForm({
|
|||||||
journalNumber,
|
journalNumber,
|
||||||
manualJournal,
|
manualJournal,
|
||||||
formatMessage,
|
formatMessage,
|
||||||
|
isNewMode,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const initialValues = useMemo(
|
const initialValues = useMemo(
|
||||||
@@ -131,7 +120,7 @@ function MakeJournalEntriesForm({
|
|||||||
entries: orderingLinesIndexes(defaultInitialValues.entries),
|
entries: orderingLinesIndexes(defaultInitialValues.entries),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
[manualJournal, journalNumber],
|
[manualJournal, baseCurrency, journalNumber],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle journal number field change.
|
// Handle journal number field change.
|
||||||
@@ -144,6 +133,7 @@ function MakeJournalEntriesForm({
|
|||||||
[changePageSubtitle],
|
[changePageSubtitle],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Handle the form submiting.
|
||||||
const handleSubmit = (values, { setErrors, setSubmitting, resetForm }) => {
|
const handleSubmit = (values, { setErrors, setSubmitting, resetForm }) => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
const entries = values.entries.filter(
|
const entries = values.entries.filter(
|
||||||
@@ -179,11 +169,13 @@ function MakeJournalEntriesForm({
|
|||||||
}
|
}
|
||||||
const form = { ...values, publish: submitPayload.publish, entries };
|
const form = { ...values, publish: submitPayload.publish, entries };
|
||||||
|
|
||||||
|
// Handle the request error.
|
||||||
const handleError = (error) => {
|
const handleError = (error) => {
|
||||||
transformErrors(error, { setErrors });
|
transformErrors(error, { setErrors });
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle the request success.
|
||||||
const handleSuccess = (errors) => {
|
const handleSuccess = (errors) => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage(
|
message: formatMessage(
|
||||||
@@ -196,7 +188,6 @@ function MakeJournalEntriesForm({
|
|||||||
),
|
),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
|
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
|
||||||
if (submitPayload.redirect) {
|
if (submitPayload.redirect) {
|
||||||
@@ -208,25 +199,14 @@ function MakeJournalEntriesForm({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isNewMode) {
|
if (isNewMode) {
|
||||||
requestMakeJournalEntries(form).then(handleSuccess).catch(handleError);
|
createJournalMutate(form).then(handleSuccess).catch(handleError);
|
||||||
} else {
|
} else {
|
||||||
requestEditManualJournal(manualJournal.id, form)
|
editJournalMutate(manualJournal.id, form)
|
||||||
.then(handleSuccess)
|
.then(handleSuccess)
|
||||||
.catch(handleError);
|
.catch(handleError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancelClick = useCallback(() => {
|
|
||||||
history.goBack();
|
|
||||||
}, [history]);
|
|
||||||
|
|
||||||
const handleSubmitClick = useCallback(
|
|
||||||
(event, payload) => {
|
|
||||||
setSubmitPayload({ ...payload });
|
|
||||||
},
|
|
||||||
[setSubmitPayload],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@@ -240,39 +220,21 @@ function MakeJournalEntriesForm({
|
|||||||
validationSchema={isNewMode ? CreateJournalSchema : EditJournalSchema}
|
validationSchema={isNewMode ? CreateJournalSchema : EditJournalSchema}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
{({ isSubmitting }) => (
|
<Form>
|
||||||
<Form>
|
<MakeJournalEntriesHeader
|
||||||
<MakeJournalEntriesHeader
|
onJournalNumberChanged={handleJournalNumberChanged}
|
||||||
manualJournal={manualJournalId}
|
/>
|
||||||
onJournalNumberChanged={handleJournalNumberChanged}
|
<MakeJournalNumberWatcher journalNumber={journalNumber} />
|
||||||
/>
|
<MakeJournalEntriesField defaultRow={defaultEntry} />
|
||||||
<MakeJournalNumberWatcher journalNumber={journalNumber} />
|
<MakeJournalFormFooter />
|
||||||
<MakeJournalEntriesField defaultRow={defaultEntry} />
|
<MakeJournalFormFloatingActions />
|
||||||
<MakeJournalFormFooter />
|
</Form>
|
||||||
<MakeJournalFormFloatingActions
|
|
||||||
isSubmitting={isSubmitting}
|
|
||||||
manualJournal={manualJournal}
|
|
||||||
// manualJournalPublished={values.status}
|
|
||||||
onCancelClick={handleCancelClick}
|
|
||||||
onSubmitClick={handleSubmitClick}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</Formik>
|
</Formik>
|
||||||
{/* <Dragzone
|
|
||||||
initialFiles={initialAttachmentFiles}
|
|
||||||
onDrop={handleDropFiles}
|
|
||||||
onDeleteFile={handleDeleteFile}
|
|
||||||
hint={'Attachments: Maxiumum size: 20MB'}
|
|
||||||
/> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withJournalsActions,
|
|
||||||
withManualJournalDetail(),
|
|
||||||
withAccountsActions,
|
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withMediaActions,
|
withMediaActions,
|
||||||
withSettings(({ manualJournalsSettings, organizationSettings }) => ({
|
withSettings(({ manualJournalsSettings, organizationSettings }) => ({
|
||||||
@@ -280,5 +242,4 @@ export default compose(
|
|||||||
journalNumberPrefix: manualJournalsSettings?.numberPrefix,
|
journalNumberPrefix: manualJournalsSettings?.numberPrefix,
|
||||||
baseCurrency: organizationSettings?.baseCurrency,
|
baseCurrency: organizationSettings?.baseCurrency,
|
||||||
})),
|
})),
|
||||||
withManualJournalsActions,
|
|
||||||
)(MakeJournalEntriesForm);
|
)(MakeJournalEntriesForm);
|
||||||
|
|||||||
@@ -21,25 +21,24 @@ import {
|
|||||||
CurrencySelectList,
|
CurrencySelectList,
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
|
||||||
|
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import withCurrencies from 'containers/Currencies/withCurrencies';
|
|
||||||
|
|
||||||
import { compose, inputIntent, handleDateChange } from 'utils';
|
import { compose, inputIntent, handleDateChange } from 'utils';
|
||||||
|
|
||||||
function MakeJournalEntriesHeader({
|
function MakeJournalEntriesHeader({
|
||||||
// #ownProps
|
// #ownProps
|
||||||
manualJournal,
|
|
||||||
onJournalNumberChanged,
|
onJournalNumberChanged,
|
||||||
|
|
||||||
// #withCurrencies
|
|
||||||
currenciesList,
|
|
||||||
|
|
||||||
// #withDialog
|
// #withDialog
|
||||||
openDialog,
|
openDialog,
|
||||||
}) {
|
}) {
|
||||||
const handleJournalNumberChange = useCallback(() => {
|
const { currencies } = useMakeJournalFormContext();
|
||||||
|
|
||||||
|
// Handle journal number change.
|
||||||
|
const handleJournalNumberChange = () => {
|
||||||
openDialog('journal-number-form', {});
|
openDialog('journal-number-form', {});
|
||||||
}, [openDialog]);
|
};
|
||||||
|
|
||||||
// Handle journal number field blur event.
|
// Handle journal number field blur event.
|
||||||
const handleJournalNumberChanged = (event) => {
|
const handleJournalNumberChanged = (event) => {
|
||||||
@@ -165,7 +164,7 @@ function MakeJournalEntriesHeader({
|
|||||||
inline={true}
|
inline={true}
|
||||||
>
|
>
|
||||||
<CurrencySelectList
|
<CurrencySelectList
|
||||||
currenciesList={currenciesList}
|
currenciesList={currencies}
|
||||||
selectedCurrencyCode={value}
|
selectedCurrencyCode={value}
|
||||||
onCurrencySelected={(currencyItem) => {
|
onCurrencySelected={(currencyItem) => {
|
||||||
form.setFieldValue('currency_code', currencyItem.currency_code);
|
form.setFieldValue('currency_code', currencyItem.currency_code);
|
||||||
@@ -181,7 +180,4 @@ function MakeJournalEntriesHeader({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
withCurrencies(({ currenciesList }) => ({
|
|
||||||
currenciesList,
|
|
||||||
})),
|
|
||||||
)(MakeJournalEntriesHeader);
|
)(MakeJournalEntriesHeader);
|
||||||
|
|||||||
@@ -1,44 +1,26 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { useParams, useHistory } from 'react-router-dom';
|
import { useParams, useHistory } from 'react-router-dom';
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
|
|
||||||
import MakeJournalEntriesForm from './MakeJournalEntriesForm';
|
import MakeJournalEntriesForm from './MakeJournalEntriesForm';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import { MakeJournalProvider } from './MakeJournalProvider';
|
||||||
|
|
||||||
import withCustomersActions from 'containers/Customers/withCustomersActions';
|
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
|
||||||
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
|
|
||||||
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
|
|
||||||
import withSettingsActions from 'containers/Settings/withSettingsActions';
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
import 'style/pages/ManualJournal/MakeJournal.scss'
|
import 'style/pages/ManualJournal/MakeJournal.scss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make journal entries page.
|
||||||
|
*/
|
||||||
function MakeJournalEntriesPage({
|
function MakeJournalEntriesPage({
|
||||||
// #withCustomersActions
|
|
||||||
requestFetchCustomers,
|
|
||||||
|
|
||||||
// #withAccountsActions
|
|
||||||
requestFetchAccounts,
|
|
||||||
|
|
||||||
// #withManualJournalActions
|
|
||||||
requestFetchManualJournal,
|
|
||||||
|
|
||||||
// #wihtCurrenciesActions
|
|
||||||
requestFetchCurrencies,
|
|
||||||
|
|
||||||
// #withSettingsActions
|
|
||||||
requestFetchOptions,
|
|
||||||
|
|
||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
setSidebarShrink,
|
setSidebarShrink,
|
||||||
resetSidebarPreviousExpand,
|
resetSidebarPreviousExpand,
|
||||||
setDashboardBackLink
|
setDashboardBackLink,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { id } = useParams();
|
const { id: journalId } = useParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Shrink the sidebar by foce.
|
// Shrink the sidebar by foce.
|
||||||
@@ -52,27 +34,7 @@ function MakeJournalEntriesPage({
|
|||||||
// Hide the back link on dashboard topbar.
|
// Hide the back link on dashboard topbar.
|
||||||
setDashboardBackLink(false);
|
setDashboardBackLink(false);
|
||||||
};
|
};
|
||||||
}, [resetSidebarPreviousExpand, setSidebarShrink]);
|
}, [resetSidebarPreviousExpand, setDashboardBackLink, setSidebarShrink]);
|
||||||
|
|
||||||
const fetchAccounts = useQuery('accounts-list', (key) =>
|
|
||||||
requestFetchAccounts(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetchCustomers = useQuery('customers-list', (key) =>
|
|
||||||
requestFetchCustomers(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetchCurrencies = useQuery('currencies', () =>
|
|
||||||
requestFetchCurrencies(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
|
|
||||||
|
|
||||||
const fetchJournal = useQuery(
|
|
||||||
['manual-journal', id],
|
|
||||||
(key, journalId) => requestFetchManualJournal(journalId),
|
|
||||||
{ enabled: id && id },
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleFormSubmit = useCallback(
|
const handleFormSubmit = useCallback(
|
||||||
(payload) => {
|
(payload) => {
|
||||||
@@ -86,29 +48,15 @@ function MakeJournalEntriesPage({
|
|||||||
}, [history]);
|
}, [history]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<MakeJournalProvider journalId={journalId}>
|
||||||
loading={
|
|
||||||
fetchJournal.isFetching ||
|
|
||||||
fetchAccounts.isFetching ||
|
|
||||||
fetchCurrencies.isFetching ||
|
|
||||||
fetchCustomers.isFetching
|
|
||||||
}
|
|
||||||
name={'make-journal-page'}
|
|
||||||
>
|
|
||||||
<MakeJournalEntriesForm
|
<MakeJournalEntriesForm
|
||||||
onFormSubmit={handleFormSubmit}
|
onFormSubmit={handleFormSubmit}
|
||||||
manualJournalId={id}
|
|
||||||
onCancelForm={handleCancel}
|
onCancelForm={handleCancel}
|
||||||
/>
|
/>
|
||||||
</DashboardInsider>
|
</MakeJournalProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withAccountsActions,
|
|
||||||
withCustomersActions,
|
|
||||||
withManualJournalsActions,
|
|
||||||
withCurrenciesActions,
|
|
||||||
withSettingsActions,
|
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
)(MakeJournalEntriesPage);
|
)(MakeJournalEntriesPage);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
|||||||
import { Button } from '@blueprintjs/core';
|
import { Button } from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import { compose, saveInvoke } from 'utils';
|
import { saveInvoke } from 'utils';
|
||||||
import {
|
import {
|
||||||
AccountsListFieldCell,
|
AccountsListFieldCell,
|
||||||
MoneyFieldCell,
|
MoneyFieldCell,
|
||||||
@@ -18,21 +18,13 @@ import {
|
|||||||
} from './components';
|
} from './components';
|
||||||
import { DataTableEditable } from 'components';
|
import { DataTableEditable } from 'components';
|
||||||
|
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
|
||||||
import withCustomers from 'containers/Customers/withCustomers';
|
|
||||||
|
|
||||||
import { updateDataReducer } from './utils';
|
import { updateDataReducer } from './utils';
|
||||||
|
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make journal entries table component.
|
* Make journal entries table component.
|
||||||
*/
|
*/
|
||||||
function MakeJournalEntriesTable({
|
export default function MakeJournalEntriesTable({
|
||||||
// #withCustomers
|
|
||||||
customers,
|
|
||||||
|
|
||||||
// #withAccounts
|
|
||||||
accountsList,
|
|
||||||
|
|
||||||
// #ownPorps
|
// #ownPorps
|
||||||
onClickRemoveRow,
|
onClickRemoveRow,
|
||||||
onClickAddNewRow,
|
onClickAddNewRow,
|
||||||
@@ -44,6 +36,8 @@ function MakeJournalEntriesTable({
|
|||||||
const [rows, setRows] = useState([]);
|
const [rows, setRows] = useState([]);
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
const { accounts, customers } = useMakeJournalFormContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRows([...entries.map((e) => ({ ...e, rowType: 'editor' }))]);
|
setRows([...entries.map((e) => ({ ...e, rowType: 'editor' }))]);
|
||||||
}, [entries, setRows]);
|
}, [entries, setRows]);
|
||||||
@@ -175,7 +169,7 @@ function MakeJournalEntriesTable({
|
|||||||
sticky={true}
|
sticky={true}
|
||||||
totalRow={true}
|
totalRow={true}
|
||||||
payload={{
|
payload={{
|
||||||
accounts: accountsList,
|
accounts,
|
||||||
errors: error,
|
errors: error,
|
||||||
updateData: handleUpdateData,
|
updateData: handleUpdateData,
|
||||||
removeRow: handleRemoveRow,
|
removeRow: handleRemoveRow,
|
||||||
@@ -209,12 +203,3 @@ function MakeJournalEntriesTable({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withAccounts(({ accountsList }) => ({
|
|
||||||
accountsList,
|
|
||||||
})),
|
|
||||||
withCustomers(({ customers }) => ({
|
|
||||||
customers,
|
|
||||||
})),
|
|
||||||
)(MakeJournalEntriesTable);
|
|
||||||
|
|||||||
@@ -12,73 +12,64 @@ import {
|
|||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { saveInvoke } from 'utils';
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import { Icon, If } from 'components';
|
import { Icon, If } from 'components';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make Journal floating actions bar.
|
* Make Journal floating actions bar.
|
||||||
*/
|
*/
|
||||||
export default function MakeJournalFloatingAction({
|
export default function MakeJournalFloatingAction() {
|
||||||
isSubmitting,
|
const history = useHistory();
|
||||||
onSubmitClick,
|
|
||||||
onCancelClick,
|
|
||||||
manualJournal,
|
|
||||||
}) {
|
|
||||||
const { submitForm, resetForm } = useFormikContext();
|
|
||||||
|
|
||||||
|
// Formik context.
|
||||||
|
const { submitForm, resetForm, isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
// Make journal form context.
|
||||||
|
const { setSubmitPayload, manualJournal } = useMakeJournalFormContext();
|
||||||
|
|
||||||
|
// Handle submit & publish button click.
|
||||||
const handleSubmitPublishBtnClick = (event) => {
|
const handleSubmitPublishBtnClick = (event) => {
|
||||||
saveInvoke(onSubmitClick, event, {
|
submitForm();
|
||||||
redirect: true,
|
setSubmitPayload({ redirect: true, publish: true });
|
||||||
publish: true,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle submit, publish & new button click.
|
||||||
const handleSubmitPublishAndNewBtnClick = (event) => {
|
const handleSubmitPublishAndNewBtnClick = (event) => {
|
||||||
submitForm();
|
submitForm();
|
||||||
saveInvoke(onSubmitClick, event, {
|
setSubmitPayload({ redirect: false, publish: true, resetForm: true });
|
||||||
redirect: false,
|
|
||||||
publish: true,
|
|
||||||
resetForm: true,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle submit, publish & edit button click.
|
||||||
const handleSubmitPublishContinueEditingBtnClick = (event) => {
|
const handleSubmitPublishContinueEditingBtnClick = (event) => {
|
||||||
submitForm();
|
submitForm();
|
||||||
saveInvoke(onSubmitClick, event, {
|
setSubmitPayload({ redirect: false, publish: true });
|
||||||
redirect: false,
|
|
||||||
publish: true,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle submit as draft button click.
|
||||||
const handleSubmitDraftBtnClick = (event) => {
|
const handleSubmitDraftBtnClick = (event) => {
|
||||||
saveInvoke(onSubmitClick, event, {
|
setSubmitPayload({ redirect: true, publish: false });
|
||||||
redirect: true,
|
|
||||||
publish: false,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle submit as draft & new button click.
|
||||||
const handleSubmitDraftAndNewBtnClick = (event) => {
|
const handleSubmitDraftAndNewBtnClick = (event) => {
|
||||||
submitForm();
|
submitForm();
|
||||||
saveInvoke(onSubmitClick, event, {
|
setSubmitPayload({ redirect: false, publish: false, resetForm: true });
|
||||||
redirect: false,
|
|
||||||
publish: false,
|
|
||||||
resetForm: true,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle submit as draft & continue editing button click.
|
||||||
const handleSubmitDraftContinueEditingBtnClick = (event) => {
|
const handleSubmitDraftContinueEditingBtnClick = (event) => {
|
||||||
submitForm();
|
submitForm();
|
||||||
saveInvoke(onSubmitClick, event, {
|
setSubmitPayload({ redirect: false, publish: false });
|
||||||
redirect: false,
|
|
||||||
publish: false,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle cancel button click.
|
||||||
const handleCancelBtnClick = (event) => {
|
const handleCancelBtnClick = (event) => {
|
||||||
saveInvoke(onCancelClick, event);
|
history.goBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle clear button click.
|
||||||
const handleClearBtnClick = (event) => {
|
const handleClearBtnClick = (event) => {
|
||||||
resetForm();
|
resetForm();
|
||||||
};
|
};
|
||||||
@@ -90,6 +81,7 @@ export default function MakeJournalFloatingAction({
|
|||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button
|
<Button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
|
loading={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
onClick={handleSubmitPublishBtnClick}
|
onClick={handleSubmitPublishBtnClick}
|
||||||
text={<T id={'save_publish'} />}
|
text={<T id={'save_publish'} />}
|
||||||
@@ -114,6 +106,7 @@ export default function MakeJournalFloatingAction({
|
|||||||
<Button
|
<Button
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
|
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
|
||||||
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
@@ -144,6 +137,7 @@ export default function MakeJournalFloatingAction({
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
|
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
|
||||||
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
@@ -173,6 +167,7 @@ export default function MakeJournalFloatingAction({
|
|||||||
<Button
|
<Button
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
|
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
|
||||||
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|||||||
@@ -9,14 +9,20 @@ import withManualJournals from './withManualJournals';
|
|||||||
import { defaultToTransform } from 'utils';
|
import { defaultToTransform } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Journal number chaلing watcher.
|
||||||
*/
|
*/
|
||||||
function MakeJournalNumberChangingWatcher({
|
function MakeJournalNumberChangingWatcher({
|
||||||
journalNumber,
|
// #withDashboardActions
|
||||||
|
changePageSubtitle,
|
||||||
|
|
||||||
|
// #withManualJournals
|
||||||
journalNumberChanged,
|
journalNumberChanged,
|
||||||
|
|
||||||
|
// #withManualJournalsActions
|
||||||
setJournalNumberChanged,
|
setJournalNumberChanged,
|
||||||
changePageSubtitle
|
|
||||||
|
// #ownProps
|
||||||
|
journalNumber,
|
||||||
}) {
|
}) {
|
||||||
const { setFieldValue } = useFormikContext();
|
const { setFieldValue } = useFormikContext();
|
||||||
|
|
||||||
|
|||||||
87
client/src/containers/Accounting/MakeJournalProvider.js
Normal file
87
client/src/containers/Accounting/MakeJournalProvider.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React, { createContext, useState } from 'react';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
import {
|
||||||
|
useAccounts,
|
||||||
|
useCustomers,
|
||||||
|
useCurrencies,
|
||||||
|
useJournal,
|
||||||
|
useCreateJournal,
|
||||||
|
useEditJournal,
|
||||||
|
useSettings
|
||||||
|
} from 'hooks/query';
|
||||||
|
|
||||||
|
const MakeJournalFormContext = createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make journal form provider.
|
||||||
|
*/
|
||||||
|
function MakeJournalProvider({ journalId, ...props }) {
|
||||||
|
// Load the accounts list.
|
||||||
|
const { data: accounts, isFetching: isAccountsLoading } = useAccounts();
|
||||||
|
|
||||||
|
// Load the customers list.
|
||||||
|
const {
|
||||||
|
data: { customers },
|
||||||
|
isFetching: isCustomersLoading,
|
||||||
|
} = useCustomers();
|
||||||
|
|
||||||
|
// Load the currencies list.
|
||||||
|
const { data: currencies, isFetching: isCurrenciesLoading } = useCurrencies();
|
||||||
|
|
||||||
|
// Load the details of the given manual journal.
|
||||||
|
const { data: manualJournal, isFetching: isJournalLoading } = useJournal(
|
||||||
|
journalId,
|
||||||
|
{
|
||||||
|
enabled: !!journalId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Create and edit journal mutations.
|
||||||
|
const { mutateAsync: createJournalMutate } = useCreateJournal();
|
||||||
|
const { mutateAsync: editJournalMutate } = useEditJournal();
|
||||||
|
|
||||||
|
// Loading the journal settings.
|
||||||
|
const { isFetching: isSettingsLoading } = useSettings();
|
||||||
|
|
||||||
|
const [submitPayload, setSubmitPayload] = useState({});
|
||||||
|
|
||||||
|
const provider = {
|
||||||
|
accounts,
|
||||||
|
customers,
|
||||||
|
currencies,
|
||||||
|
manualJournal,
|
||||||
|
|
||||||
|
createJournalMutate,
|
||||||
|
editJournalMutate,
|
||||||
|
|
||||||
|
isAccountsLoading,
|
||||||
|
isCustomersLoading,
|
||||||
|
isCurrenciesLoading,
|
||||||
|
isJournalLoading,
|
||||||
|
isSettingsLoading,
|
||||||
|
|
||||||
|
isNewMode: !journalId,
|
||||||
|
|
||||||
|
submitPayload,
|
||||||
|
setSubmitPayload
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider
|
||||||
|
loading={
|
||||||
|
isJournalLoading ||
|
||||||
|
isAccountsLoading ||
|
||||||
|
isCurrenciesLoading ||
|
||||||
|
isCustomersLoading ||
|
||||||
|
isSettingsLoading
|
||||||
|
}
|
||||||
|
name={'make-journal-page'}
|
||||||
|
>
|
||||||
|
<MakeJournalFormContext.Provider value={provider} {...props} />
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useMakeJournalFormContext = () =>
|
||||||
|
React.useContext(MakeJournalFormContext);
|
||||||
|
|
||||||
|
export { MakeJournalProvider, useMakeJournalFormContext };
|
||||||
@@ -1,23 +1,21 @@
|
|||||||
import React, { useMemo, useState, useCallback } from 'react';
|
import React from 'react';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
Classes,
|
Classes,
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
MenuItem,
|
|
||||||
Menu,
|
|
||||||
Popover,
|
Popover,
|
||||||
PopoverInteractionKind,
|
PopoverInteractionKind,
|
||||||
Position,
|
Position,
|
||||||
Intent,
|
Intent,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useRouteMatch, useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import FilterDropdown from 'components/FilterDropdown';
|
import { useManualJournalsContext } from 'containers/Accounting/ManualJournalsListProvider';
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
@@ -33,53 +31,24 @@ import { compose } from 'utils';
|
|||||||
* Manual journal actions bar.
|
* Manual journal actions bar.
|
||||||
*/
|
*/
|
||||||
function ManualJournalActionsBar({
|
function ManualJournalActionsBar({
|
||||||
// #withResourceDetail
|
|
||||||
resourceFields,
|
|
||||||
|
|
||||||
// #withManualJournals
|
|
||||||
manualJournalsViews,
|
|
||||||
|
|
||||||
// #withManualJournalsActions
|
// #withManualJournalsActions
|
||||||
addManualJournalsTableQueries,
|
addManualJournalsTableQueries,
|
||||||
changeManualJournalCurrentView,
|
|
||||||
|
|
||||||
onFilterChanged,
|
|
||||||
selectedRows = [],
|
|
||||||
onBulkDelete,
|
|
||||||
}) {
|
}) {
|
||||||
const [filterCount, setFilterCount] = useState(0);
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const { journalsViews } = useManualJournalsContext();
|
||||||
|
|
||||||
const onClickNewManualJournal = useCallback(() => {
|
// Handle click a new manual journal.
|
||||||
|
const onClickNewManualJournal = () => {
|
||||||
history.push('/make-journal-entry');
|
history.push('/make-journal-entry');
|
||||||
}, [history]);
|
};
|
||||||
|
|
||||||
// const filterDropdown = FilterDropdown({
|
|
||||||
// fields: resourceFields,
|
|
||||||
// initialCondition: {
|
|
||||||
// fieldKey: 'journal_number',
|
|
||||||
// compatator: 'contains',
|
|
||||||
// value: '',
|
|
||||||
// },
|
|
||||||
// onFilterChange: (filterConditions) => {
|
|
||||||
// setFilterCount(filterConditions.length || 0);
|
|
||||||
// addManualJournalsTableQueries({
|
|
||||||
// filter_roles: filterConditions || '',
|
|
||||||
// });
|
|
||||||
// onFilterChanged && onFilterChanged(filterConditions);
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
|
|
||||||
selectedRows,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Handle delete button click.
|
// Handle delete button click.
|
||||||
const handleBulkDelete = useCallback(() => {
|
const handleBulkDelete = () => {
|
||||||
onBulkDelete && onBulkDelete(selectedRows.map((r) => r.id));
|
|
||||||
}, [onBulkDelete, selectedRows]);
|
};
|
||||||
|
|
||||||
|
// Handle tab change.
|
||||||
const handleTabChange = (viewId) => {
|
const handleTabChange = (viewId) => {
|
||||||
changeManualJournalCurrentView(viewId.id || -1);
|
|
||||||
addManualJournalsTableQueries({
|
addManualJournalsTableQueries({
|
||||||
custom_view_id: viewId.id || null,
|
custom_view_id: viewId.id || null,
|
||||||
});
|
});
|
||||||
@@ -90,7 +59,7 @@ function ManualJournalActionsBar({
|
|||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<DashboardActionViewsList
|
<DashboardActionViewsList
|
||||||
resourceName={'manual-journals'}
|
resourceName={'manual-journals'}
|
||||||
views={manualJournalsViews}
|
views={journalsViews}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
@@ -109,14 +78,14 @@ function ManualJournalActionsBar({
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter', {
|
className={classNames(Classes.MINIMAL, 'button--filter', {
|
||||||
'has-active-filters': filterCount > 0,
|
'has-active-filters': false,
|
||||||
})}
|
})}
|
||||||
text="Filter"
|
text="Filter"
|
||||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<If condition={hasSelectedRows}>
|
<If condition={false}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
|
|||||||
15
client/src/containers/Accounting/ManualJournalsAlerts.js
Normal file
15
client/src/containers/Accounting/ManualJournalsAlerts.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import JournalDeleteAlert from 'containers/Alerts/ManualJournals/JournalDeleteAlert';
|
||||||
|
import JournalPublishAlert from 'containers/Alerts/ManualJournals/JournalPublishAlert';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manual journals alerts.
|
||||||
|
*/
|
||||||
|
export default function ManualJournalsAlerts() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<JournalDeleteAlert name={'journal-delete'} />
|
||||||
|
<JournalPublishAlert name={'journal-publish'} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -3,13 +3,11 @@ import {
|
|||||||
Intent,
|
Intent,
|
||||||
Button,
|
Button,
|
||||||
Popover,
|
Popover,
|
||||||
Tooltip,
|
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuDivider,
|
MenuDivider,
|
||||||
Position,
|
Position,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { withRouter, useParams } from 'react-router-dom';
|
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -17,68 +15,59 @@ import classNames from 'classnames';
|
|||||||
import {
|
import {
|
||||||
DataTable,
|
DataTable,
|
||||||
If,
|
If,
|
||||||
Money,
|
|
||||||
Choose,
|
Choose,
|
||||||
Icon,
|
Icon,
|
||||||
LoadingIndicator,
|
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import { useIsValuePassed } from 'hooks';
|
|
||||||
|
|
||||||
import ManualJournalsEmptyStatus from './ManualJournalsEmptyStatus';
|
import ManualJournalsEmptyStatus from './ManualJournalsEmptyStatus';
|
||||||
|
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||||
|
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||||
import {
|
import {
|
||||||
AmountPopoverContent,
|
|
||||||
NoteAccessor,
|
NoteAccessor,
|
||||||
StatusAccessor,
|
StatusAccessor,
|
||||||
|
DateAccessor,
|
||||||
|
AmountAccessor
|
||||||
} from './components';
|
} from './components';
|
||||||
|
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
|
||||||
import withManualJournals from 'containers/Accounting/withManualJournals';
|
|
||||||
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
|
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
|
||||||
|
|
||||||
import { compose, saveInvoke } from 'utils';
|
import { compose, saveInvoke } from 'utils';
|
||||||
|
import { useManualJournalsContext } from './ManualJournalsListProvider';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manual journals data-table.
|
||||||
|
*/
|
||||||
function ManualJournalsDataTable({
|
function ManualJournalsDataTable({
|
||||||
// #withManualJournals
|
|
||||||
manualJournalsCurrentPage,
|
|
||||||
manualJournalsLoading,
|
|
||||||
manualJournalsPagination,
|
|
||||||
manualJournalsTableQuery,
|
|
||||||
manualJournalsCurrentViewId,
|
|
||||||
|
|
||||||
// #withManualJournalsActions
|
// #withManualJournalsActions
|
||||||
addManualJournalsTableQueries,
|
addManualJournalsTableQueries,
|
||||||
|
|
||||||
// #ownProps
|
// #ownProps
|
||||||
onEditJournal,
|
|
||||||
onDeleteJournal,
|
|
||||||
onPublishJournal,
|
|
||||||
onSelectedRowsChange,
|
onSelectedRowsChange,
|
||||||
manualJournalViewLoading,
|
|
||||||
}) {
|
}) {
|
||||||
const { custom_view_id: customViewId } = useParams();
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const isLoadedBefore = useIsValuePassed(manualJournalsLoading, false);
|
|
||||||
|
const { manualJournals, isManualJournalsLoading } = useManualJournalsContext();
|
||||||
|
|
||||||
const handlePublishJournal = useCallback(
|
const handlePublishJournal = useCallback(
|
||||||
(journal) => () => {
|
(journal) => () => {
|
||||||
saveInvoke(onPublishJournal, journal);
|
|
||||||
},
|
},
|
||||||
[onPublishJournal],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleEditJournal = useCallback(
|
const handleEditJournal = useCallback(
|
||||||
(journal) => () => {
|
(journal) => () => {
|
||||||
saveInvoke(onEditJournal, journal);
|
|
||||||
},
|
},
|
||||||
[onEditJournal],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeleteJournal = useCallback(
|
const handleDeleteJournal = useCallback(
|
||||||
(journal) => () => {
|
(journal) => () => {
|
||||||
saveInvoke(onDeleteJournal, journal);
|
|
||||||
},
|
},
|
||||||
[onDeleteJournal],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const actionMenuList = useCallback(
|
const actionMenuList = useCallback(
|
||||||
@@ -127,22 +116,14 @@ function ManualJournalsDataTable({
|
|||||||
{
|
{
|
||||||
id: 'date',
|
id: 'date',
|
||||||
Header: formatMessage({ id: 'date' }),
|
Header: formatMessage({ id: 'date' }),
|
||||||
accessor: (r) => moment(r.date).format('YYYY MMM DD'),
|
accessor: DateAccessor,
|
||||||
width: 115,
|
width: 115,
|
||||||
className: 'date',
|
className: 'date',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'amount',
|
id: 'amount',
|
||||||
Header: formatMessage({ id: 'amount' }),
|
Header: formatMessage({ id: 'amount' }),
|
||||||
accessor: (r) => (
|
accessor: AmountAccessor,
|
||||||
<Tooltip
|
|
||||||
content={<AmountPopoverContent journalEntries={r.entries} />}
|
|
||||||
position={Position.RIGHT_TOP}
|
|
||||||
boundary={'viewport'}
|
|
||||||
>
|
|
||||||
<Money amount={r.amount} currency={'USD'} />
|
|
||||||
</Tooltip>
|
|
||||||
),
|
|
||||||
className: 'amount',
|
className: 'amount',
|
||||||
width: 115,
|
width: 115,
|
||||||
},
|
},
|
||||||
@@ -227,66 +208,42 @@ function ManualJournalsDataTable({
|
|||||||
[onSelectedRowsChange],
|
[onSelectedRowsChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const showEmptyStatus = [
|
|
||||||
manualJournalsCurrentViewId === -1,
|
|
||||||
manualJournalsCurrentPage.length === 0,
|
|
||||||
].every((condition) => condition === true);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
|
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
|
||||||
<LoadingIndicator
|
<Choose>
|
||||||
loading={
|
<Choose.When condition={false}>
|
||||||
(manualJournalsLoading && !isLoadedBefore) || manualJournalViewLoading
|
<ManualJournalsEmptyStatus />
|
||||||
}
|
</Choose.When>
|
||||||
>
|
|
||||||
<Choose>
|
|
||||||
<Choose.When condition={showEmptyStatus}>
|
|
||||||
<ManualJournalsEmptyStatus />
|
|
||||||
</Choose.When>
|
|
||||||
|
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<DataTable
|
<DataTable
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={manualJournalsCurrentPage}
|
data={manualJournals}
|
||||||
onFetchData={handleDataTableFetchData}
|
onFetchData={handleDataTableFetchData}
|
||||||
manualSortBy={true}
|
manualSortBy={true}
|
||||||
selectionColumn={true}
|
selectionColumn={true}
|
||||||
expandable={true}
|
expandable={true}
|
||||||
sticky={true}
|
sticky={true}
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
loading={isManualJournalsLoading}
|
||||||
rowContextMenu={onRowContextMenu}
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
pagesCount={manualJournalsPagination.pagesCount}
|
rowContextMenu={onRowContextMenu}
|
||||||
pagination={true}
|
// pagesCount={manualJournalsPagination.pagesCount}
|
||||||
initialPageSize={manualJournalsTableQuery.page_size}
|
pagination={true}
|
||||||
initialPageIndex={manualJournalsTableQuery.page - 1}
|
// initialPageSize={manualJournalsTableQuery.page_size}
|
||||||
autoResetSortBy={false}
|
// initialPageIndex={manualJournalsTableQuery.page - 1}
|
||||||
autoResetPage={false}
|
autoResetSortBy={false}
|
||||||
/>
|
autoResetPage={false}
|
||||||
</Choose.Otherwise>
|
|
||||||
</Choose>
|
TableLoadingRenderer={TableSkeletonRows}
|
||||||
</LoadingIndicator>
|
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||||
|
/>
|
||||||
|
</Choose.Otherwise>
|
||||||
|
</Choose>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withRouter,
|
|
||||||
withDialogActions,
|
|
||||||
withManualJournalsActions,
|
withManualJournalsActions,
|
||||||
withManualJournals(
|
|
||||||
({
|
|
||||||
manualJournalsCurrentPage,
|
|
||||||
manualJournalsLoading,
|
|
||||||
manualJournalsPagination,
|
|
||||||
manualJournalsTableQuery,
|
|
||||||
manualJournalsCurrentViewId,
|
|
||||||
}) => ({
|
|
||||||
manualJournalsCurrentPage,
|
|
||||||
manualJournalsLoading,
|
|
||||||
manualJournalsPagination,
|
|
||||||
manualJournalsTableQuery,
|
|
||||||
manualJournalsCurrentViewId,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)(ManualJournalsDataTable);
|
)(ManualJournalsDataTable);
|
||||||
|
|||||||
@@ -1,31 +1,26 @@
|
|||||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
import React, { useEffect, useCallback } from 'react';
|
||||||
import { Route, Switch, useHistory, withRouter } from 'react-router-dom';
|
import { Route, Switch, useHistory } from 'react-router-dom';
|
||||||
import { queryCache, useQuery } from 'react-query';
|
|
||||||
import { Alert, Intent } from '@blueprintjs/core';
|
|
||||||
import AppToaster from 'components/AppToaster';
|
|
||||||
import {
|
import {
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
useIntl,
|
useIntl,
|
||||||
} from 'react-intl';
|
} from 'react-intl';
|
||||||
|
|
||||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
|
||||||
|
|
||||||
import ManualJournalsViewTabs from 'containers/Accounting/ManualJournalsViewTabs';
|
import { ManualJournalsListProvider } from './ManualJournalsListProvider';
|
||||||
import ManualJournalsDataTable from 'containers/Accounting/ManualJournalsDataTable';
|
import ManualJournalsAlerts from './ManualJournalsAlerts';
|
||||||
import ManualJournalsActionsBar from 'containers/Accounting/ManualJournalActionsBar';
|
import ManualJournalsViewTabs from './ManualJournalsViewTabs';
|
||||||
|
import ManualJournalsDataTable from './ManualJournalsDataTable';
|
||||||
|
import ManualJournalsActionsBar from './ManualJournalActionsBar';
|
||||||
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withManualJournals from 'containers/Accounting/withManualJournals';
|
import withManualJournals from 'containers/Accounting/withManualJournals';
|
||||||
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
|
|
||||||
import withViewsActions from 'containers/Views/withViewsActions';
|
|
||||||
import withRouteActions from 'containers/Router/withRouteActions';
|
|
||||||
import withResourceActions from 'containers/Resources/withResourcesActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
import 'style/pages/ManualJournal/List.scss';
|
import 'style/pages/ManualJournal/List.scss';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manual journals table.
|
* Manual journals table.
|
||||||
*/
|
*/
|
||||||
@@ -33,117 +28,17 @@ function ManualJournalsTable({
|
|||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
|
||||||
// #withViewsActions
|
|
||||||
requestFetchResourceViews,
|
|
||||||
|
|
||||||
// #withResourceActions
|
|
||||||
requestFetchResourceFields,
|
|
||||||
|
|
||||||
// #withManualJournals
|
// #withManualJournals
|
||||||
manualJournalsTableQuery,
|
manualJournalsTableQuery,
|
||||||
|
|
||||||
// #withManualJournalsActions
|
|
||||||
requestFetchManualJournalsTable,
|
|
||||||
requestDeleteManualJournal,
|
|
||||||
requestPublishManualJournal,
|
|
||||||
requestDeleteBulkManualJournals,
|
|
||||||
addManualJournalsTableQueries,
|
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const [deleteManualJournal, setDeleteManualJournal] = useState(false);
|
|
||||||
const [selectedRows, setSelectedRows] = useState([]);
|
|
||||||
const [bulkDelete, setBulkDelete] = useState(false);
|
|
||||||
const [publishManualJournal, setPublishManualJournal] = useState(false);
|
|
||||||
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const fetchResourceViews = useQuery(
|
// Handle update the page title.
|
||||||
['resource-views', 'manual-journals'],
|
|
||||||
() => requestFetchResourceViews('manual_journals'),
|
|
||||||
);
|
|
||||||
|
|
||||||
// const fetchResourceFields = useQuery(
|
|
||||||
// ['resource-fields', 'manual-journals'],
|
|
||||||
// () => requestFetchResourceFields('manual_journals'),
|
|
||||||
// );
|
|
||||||
|
|
||||||
const fetchManualJournals = useQuery(
|
|
||||||
['manual-journals-table', manualJournalsTableQuery],
|
|
||||||
(key, q) => requestFetchManualJournalsTable(),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle(formatMessage({ id: 'manual_journals' }));
|
changePageTitle(formatMessage({ id: 'manual_journals' }));
|
||||||
}, [changePageTitle, formatMessage]);
|
}, [changePageTitle, formatMessage]);
|
||||||
|
|
||||||
// Handle delete manual journal click.
|
|
||||||
const handleDeleteJournal = useCallback(
|
|
||||||
(journal) => {
|
|
||||||
setDeleteManualJournal(journal);
|
|
||||||
},
|
|
||||||
[setDeleteManualJournal],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle cancel delete manual journal.
|
|
||||||
const handleCancelManualJournalDelete = useCallback(() => {
|
|
||||||
setDeleteManualJournal(false);
|
|
||||||
}, [setDeleteManualJournal]);
|
|
||||||
|
|
||||||
// Handle confirm delete manual journal.
|
|
||||||
const handleConfirmManualJournalDelete = useCallback(() => {
|
|
||||||
requestDeleteManualJournal(deleteManualJournal.id).then(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage(
|
|
||||||
{ id: 'the_journal_has_been_deleted_successfully' },
|
|
||||||
{ number: deleteManualJournal.journal_number },
|
|
||||||
),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
setDeleteManualJournal(false);
|
|
||||||
});
|
|
||||||
}, [deleteManualJournal, requestDeleteManualJournal, formatMessage]);
|
|
||||||
|
|
||||||
// Calculates the selected rows count.
|
|
||||||
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
|
|
||||||
selectedRows,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleBulkDelete = useCallback(
|
|
||||||
(accountsIds) => {
|
|
||||||
setBulkDelete(accountsIds);
|
|
||||||
},
|
|
||||||
[setBulkDelete],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle confirm journals bulk delete.
|
|
||||||
const handleConfirmBulkDelete = useCallback(() => {
|
|
||||||
requestDeleteBulkManualJournals(bulkDelete)
|
|
||||||
.then(() => {
|
|
||||||
setBulkDelete(false);
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage(
|
|
||||||
{ id: 'the_journals_has_been_deleted_successfully' },
|
|
||||||
{ count: selectedRowsCount },
|
|
||||||
),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setBulkDelete(false);
|
|
||||||
});
|
|
||||||
}, [
|
|
||||||
requestDeleteBulkManualJournals,
|
|
||||||
bulkDelete,
|
|
||||||
formatMessage,
|
|
||||||
selectedRowsCount,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Handle cancel bulk delete alert.
|
|
||||||
const handleCancelBulkDelete = useCallback(() => {
|
|
||||||
setBulkDelete(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleEditJournal = useCallback(
|
const handleEditJournal = useCallback(
|
||||||
(journal) => {
|
(journal) => {
|
||||||
history.push(`/manual-journals/${journal.id}/edit`);
|
history.push(`/manual-journals/${journal.id}/edit`);
|
||||||
@@ -154,79 +49,9 @@ function ManualJournalsTable({
|
|||||||
// Handle filter change to re-fetch data-table.
|
// Handle filter change to re-fetch data-table.
|
||||||
const handleFilterChanged = useCallback(() => {}, []);
|
const handleFilterChanged = useCallback(() => {}, []);
|
||||||
|
|
||||||
// Handle view change to re-fetch data table.
|
|
||||||
// const handleViewChanged = useCallback(() => {
|
|
||||||
|
|
||||||
// }, [fetchManualJournals]);
|
|
||||||
|
|
||||||
// Handle fetch data of manual jouranls datatable.
|
|
||||||
const handleFetchData = useCallback(
|
|
||||||
({ pageIndex, pageSize, sortBy }) => {
|
|
||||||
const page = pageIndex + 1;
|
|
||||||
|
|
||||||
addManualJournalsTableQueries({
|
|
||||||
...(sortBy.length > 0
|
|
||||||
? {
|
|
||||||
column_sort_by: sortBy[0].id,
|
|
||||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
page_size: pageSize,
|
|
||||||
page,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[addManualJournalsTableQueries],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle publish manual Journal click.
|
|
||||||
const handlePublishMaunalJournal = useCallback(
|
|
||||||
(jouranl) => {
|
|
||||||
setPublishManualJournal(jouranl);
|
|
||||||
},
|
|
||||||
[setPublishManualJournal],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle cancel manual journal alert.
|
|
||||||
const handleCancelPublishMaunalJournal = useCallback(() => {
|
|
||||||
setPublishManualJournal(false);
|
|
||||||
}, [setPublishManualJournal]);
|
|
||||||
|
|
||||||
// Handle publish manual journal confirm.
|
|
||||||
const handleConfirmPublishManualJournal = useCallback(() => {
|
|
||||||
requestPublishManualJournal(publishManualJournal.id)
|
|
||||||
.then(() => {
|
|
||||||
setPublishManualJournal(false);
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'the_manual_journal_has_been_published',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
queryCache.invalidateQueries('manual-journals-table');
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setPublishManualJournal(false);
|
|
||||||
});
|
|
||||||
}, [publishManualJournal, requestPublishManualJournal, formatMessage]);
|
|
||||||
|
|
||||||
// Handle selected rows change.
|
|
||||||
const handleSelectedRowsChange = useCallback(
|
|
||||||
(accounts) => {
|
|
||||||
setSelectedRows(accounts);
|
|
||||||
},
|
|
||||||
[setSelectedRows],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<ManualJournalsListProvider query={manualJournalsTableQuery}>
|
||||||
loading={fetchResourceViews.isFetching}
|
<ManualJournalsActionsBar />
|
||||||
name={'manual-journals'}
|
|
||||||
>
|
|
||||||
<ManualJournalsActionsBar
|
|
||||||
onBulkDelete={handleBulkDelete}
|
|
||||||
selectedRows={selectedRows}
|
|
||||||
onFilterChanged={handleFilterChanged}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
<Switch>
|
<Switch>
|
||||||
@@ -238,72 +63,17 @@ function ManualJournalsTable({
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<ManualJournalsViewTabs />
|
<ManualJournalsViewTabs />
|
||||||
|
<ManualJournalsDataTable />
|
||||||
<ManualJournalsDataTable
|
<ManualJournalsAlerts />
|
||||||
onDeleteJournal={handleDeleteJournal}
|
|
||||||
onEditJournal={handleEditJournal}
|
|
||||||
onPublishJournal={handlePublishMaunalJournal}
|
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
|
||||||
manualJournalViewLoading={fetchManualJournals.isFetching}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
||||||
<Alert
|
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
|
||||||
confirmButtonText={<T id={'delete'} />}
|
|
||||||
icon="trash"
|
|
||||||
intent={Intent.DANGER}
|
|
||||||
isOpen={deleteManualJournal}
|
|
||||||
onCancel={handleCancelManualJournalDelete}
|
|
||||||
onConfirm={handleConfirmManualJournalDelete}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<T id={'once_delete_this_journal_you_will_able_to_restore_it'} />
|
|
||||||
</p>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<Alert
|
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
|
||||||
confirmButtonText={
|
|
||||||
<T id={'delete_count'} values={{ count: selectedRowsCount }} />
|
|
||||||
}
|
|
||||||
icon="trash"
|
|
||||||
intent={Intent.DANGER}
|
|
||||||
isOpen={bulkDelete}
|
|
||||||
onCancel={handleCancelBulkDelete}
|
|
||||||
onConfirm={handleConfirmBulkDelete}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<T
|
|
||||||
id={'once_delete_these_journals_you_will_not_able_restore_them'}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</Alert>
|
|
||||||
<Alert
|
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
|
||||||
confirmButtonText={<T id={'publish'} />}
|
|
||||||
intent={Intent.WARNING}
|
|
||||||
isOpen={publishManualJournal}
|
|
||||||
onCancel={handleCancelPublishMaunalJournal}
|
|
||||||
onConfirm={handleConfirmPublishManualJournal}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<T id={'are_sure_to_publish_this_manual_journal'} />
|
|
||||||
</p>
|
|
||||||
</Alert>
|
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</DashboardInsider>
|
</ManualJournalsListProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withRouter,
|
|
||||||
withRouteActions,
|
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withManualJournalsActions,
|
|
||||||
withViewsActions,
|
|
||||||
withResourceActions,
|
|
||||||
withManualJournals(({ manualJournalsTableQuery }) => ({
|
withManualJournals(({ manualJournalsTableQuery }) => ({
|
||||||
manualJournalsTableQuery,
|
manualJournalsTableQuery,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import React, { createContext } from 'react';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
import { useResourceViews, useJournals } from 'hooks/query';
|
||||||
|
|
||||||
|
const ManualJournalsContext = createContext();
|
||||||
|
|
||||||
|
function ManualJournalsListProvider({ query, ...props }) {
|
||||||
|
// Fetches accounts resource views and fields.
|
||||||
|
const { data: journalsViews, isFetching: isViewsLoading } = useResourceViews(
|
||||||
|
'manual_journals',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetches the manual journals transactions with pagination meta.
|
||||||
|
const {
|
||||||
|
data: { manualJournals },
|
||||||
|
isFetching: isManualJournalsLoading,
|
||||||
|
} = useJournals(query);
|
||||||
|
|
||||||
|
// Global state.
|
||||||
|
const state = {
|
||||||
|
manualJournals,
|
||||||
|
journalsViews,
|
||||||
|
|
||||||
|
isManualJournalsLoading,
|
||||||
|
isViewsLoading,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider loading={isViewsLoading} name={'manual-journals'}>
|
||||||
|
<ManualJournalsContext.Provider value={state} {...props} />
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useManualJournalsContext = () => React.useContext(ManualJournalsContext);
|
||||||
|
|
||||||
|
export { ManualJournalsListProvider, useManualJournalsContext };
|
||||||
@@ -1,61 +1,38 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React from 'react';
|
||||||
import { useHistory } from 'react-router';
|
|
||||||
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
|
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
|
||||||
import { useParams, withRouter } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
import { DashboardViewsTabs, Icon } from 'components';
|
import { DashboardViewsTabs } from 'components';
|
||||||
|
|
||||||
import withManualJournals from './withManualJournals';
|
import { useManualJournalsContext } from './ManualJournalsListProvider';
|
||||||
import withManualJournalsActions from './withManualJournalsActions';
|
import withManualJournalsActions from './withManualJournalsActions';
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withViewDetail from 'containers/Views/withViewDetails';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose, saveInvoke } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manual journal views tabs.
|
* Manual journal views tabs.
|
||||||
*/
|
*/
|
||||||
function ManualJournalsViewTabs({
|
function ManualJournalsViewTabs({
|
||||||
// #withViewDetail
|
|
||||||
viewId,
|
|
||||||
viewItem,
|
|
||||||
|
|
||||||
// #withManualJournals
|
|
||||||
manualJournalsViews,
|
|
||||||
|
|
||||||
// #withManualJournalsActions
|
// #withManualJournalsActions
|
||||||
addManualJournalsTableQueries,
|
addManualJournalsTableQueries,
|
||||||
changeManualJournalCurrentView,
|
|
||||||
|
|
||||||
// #withDashboardActions
|
|
||||||
setTopbarEditView,
|
|
||||||
changePageSubtitle,
|
|
||||||
|
|
||||||
// #ownProps
|
|
||||||
onViewChanged,
|
|
||||||
}) {
|
}) {
|
||||||
const { custom_view_id: customViewId } = useParams();
|
const { custom_view_id: customViewId } = useParams();
|
||||||
|
const { journalsViews } = useManualJournalsContext();
|
||||||
|
|
||||||
useEffect(() => {
|
const tabs = journalsViews.map((view) => ({
|
||||||
setTopbarEditView(customViewId);
|
|
||||||
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
|
|
||||||
}, [customViewId]);
|
|
||||||
|
|
||||||
const tabs = manualJournalsViews.map((view) => ({
|
|
||||||
...pick(view, ['name', 'id']),
|
...pick(view, ['name', 'id']),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const handleClickNewView = () => {};
|
const handleClickNewView = () => {};
|
||||||
|
|
||||||
const handleTabChange = (viewId) => {
|
const handleTabChange = (viewId) => {
|
||||||
changeManualJournalCurrentView(viewId || -1);
|
|
||||||
addManualJournalsTableQueries({
|
addManualJournalsTableQueries({
|
||||||
custom_view_id: viewId || null,
|
custom_view_id: viewId || null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar className="navbar--dashboard-views">
|
<Navbar className="navbar--dashboard-views">
|
||||||
<NavbarGroup align={Alignment.LEFT}>
|
<NavbarGroup align={Alignment.LEFT}>
|
||||||
@@ -71,19 +48,7 @@ function ManualJournalsViewTabs({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => ({
|
|
||||||
viewId: parseInt(ownProps.match.params.custom_view_id, 10),
|
|
||||||
});
|
|
||||||
|
|
||||||
const withManualJournalsViewTabs = connect(mapStateToProps);
|
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withRouter,
|
|
||||||
withManualJournalsViewTabs,
|
|
||||||
withManualJournals(({ manualJournalsViews }) => ({
|
|
||||||
manualJournalsViews,
|
|
||||||
})),
|
|
||||||
withManualJournalsActions,
|
withManualJournalsActions,
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withViewDetail(),
|
|
||||||
)(ManualJournalsViewTabs);
|
)(ManualJournalsViewTabs);
|
||||||
|
|||||||
@@ -1,10 +1,31 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Intent, Classes, Tooltip, Position, Tag, Button } from '@blueprintjs/core';
|
import {
|
||||||
|
Intent,
|
||||||
|
Classes,
|
||||||
|
Tooltip,
|
||||||
|
Position,
|
||||||
|
Tag,
|
||||||
|
Button,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import moment from 'moment';
|
||||||
import { Choose, Money, If, Icon, Hint } from 'components';
|
import { Choose, Money, If, Icon, Hint } from 'components';
|
||||||
|
|
||||||
import withAccountDetails from 'containers/Accounts/withAccountDetail';
|
import withAccountDetails from 'containers/Accounts/withAccountDetail';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
// Amount accessor.
|
||||||
|
export const AmountAccessor = (r) => (
|
||||||
|
<Tooltip
|
||||||
|
content={<AmountPopoverContent journalEntries={r.entries} />}
|
||||||
|
position={Position.RIGHT_TOP}
|
||||||
|
boundary={'viewport'}
|
||||||
|
>
|
||||||
|
<Money amount={r.amount} currency={'USD'} />
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
const AmountPopoverContentLineRender = ({
|
const AmountPopoverContentLineRender = ({
|
||||||
journalEntry,
|
journalEntry,
|
||||||
accountId,
|
accountId,
|
||||||
@@ -77,6 +98,13 @@ export const StatusAccessor = (row) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date accessor.
|
||||||
|
*/
|
||||||
|
export const DateAccessor = (row) => {
|
||||||
|
return moment(row.date).format('YYYY MMM DD');
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note column accessor.
|
* Note column accessor.
|
||||||
*/
|
*/
|
||||||
@@ -156,7 +184,11 @@ export const TotalCreditDebitCellRenderer = (chainedComponent, type) => (
|
|||||||
return computed;
|
return computed;
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
return <span><Money amount={total} currency={'USD'} /></span>;
|
return (
|
||||||
|
<span>
|
||||||
|
<Money amount={total} currency={'USD'} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return chainedComponent(props);
|
return chainedComponent(props);
|
||||||
};
|
};
|
||||||
|
|||||||
30
client/src/containers/Accounts/AccountActionsMenu.js
Normal file
30
client/src/containers/Accounts/AccountActionsMenu.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Menu, MenuItem, MenuDivider, Intent } from '@blueprintjs/core';
|
||||||
|
import { Icon, If } from 'components';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import withDialogs from 'containers/Dialog/withDialogActions';
|
||||||
|
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
import { formatMessage } from 'services/intl';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account actions menu list.
|
||||||
|
*/
|
||||||
|
export default function AccountActionsMenuList({
|
||||||
|
account,
|
||||||
|
|
||||||
|
// #withAlert
|
||||||
|
openAlert,
|
||||||
|
openDialog,
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// export default compose(withDialogs, withAlertsActions)(AccountActionsMenuList);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { memo, useState } from 'react';
|
import React from 'react';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -11,16 +11,14 @@ import {
|
|||||||
Intent,
|
Intent,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { If, DashboardActionViewsList } from 'components';
|
import { If, DashboardActionViewsList } from 'components';
|
||||||
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import FilterDropdown from 'components/FilterDropdown';
|
import FilterDropdown from 'components/FilterDropdown';
|
||||||
|
|
||||||
|
import { useAccountsChartContext } from 'containers/Accounts/AccountsChartProvider';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
|
||||||
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
@@ -30,61 +28,39 @@ import { compose } from 'utils';
|
|||||||
* Accounts actions bar.
|
* Accounts actions bar.
|
||||||
*/
|
*/
|
||||||
function AccountsActionsBar({
|
function AccountsActionsBar({
|
||||||
|
// #withDialogActions
|
||||||
openDialog,
|
openDialog,
|
||||||
accountsViews,
|
|
||||||
|
|
||||||
// #withResourceDetail
|
|
||||||
resourceFields,
|
|
||||||
|
|
||||||
// #withAccountsTableActions
|
|
||||||
addAccountsTableQueries,
|
|
||||||
setAccountsBulkAction,
|
|
||||||
|
|
||||||
// #withAccounts
|
// #withAccounts
|
||||||
accountsTableQuery,
|
|
||||||
accountsSelectedRows,
|
accountsSelectedRows,
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
openAlert,
|
openAlert,
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
onFilterChanged,
|
onFilterChanged,
|
||||||
}) {
|
}) {
|
||||||
const [filterCount, setFilterCount] = useState(
|
const { resourceViews } = useAccountsChartContext();
|
||||||
accountsTableQuery?.filter_roles?.length || 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClickNewAccount = () => {
|
const onClickNewAccount = () => {
|
||||||
openDialog('account-form', {});
|
openDialog('account-form', {});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Filter dropdown.
|
// handle bulk accounts delete.
|
||||||
const filterDropdown = FilterDropdown({
|
|
||||||
fields: resourceFields,
|
|
||||||
initialConditions: accountsTableQuery.filter_roles,
|
|
||||||
initialCondition: {
|
|
||||||
fieldKey: 'name',
|
|
||||||
comparator: 'contains',
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
onFilterChange: (filterConditions) => {
|
|
||||||
setFilterCount(filterConditions.length || 0);
|
|
||||||
addAccountsTableQueries({
|
|
||||||
filter_roles: filterConditions || '',
|
|
||||||
});
|
|
||||||
onFilterChanged && onFilterChanged(filterConditions);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleBulkDelete = () => {
|
const handleBulkDelete = () => {
|
||||||
openAlert('accounts-bulk-delete', { accountsIds: accountsSelectedRows });
|
openAlert('accounts-bulk-delete', { accountsIds: accountsSelectedRows });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle bulk accounts activate.
|
||||||
const handelBulkActivate = () => {
|
const handelBulkActivate = () => {
|
||||||
openAlert('accounts-bulk-activate', { accountsIds: accountsSelectedRows });
|
openAlert('accounts-bulk-activate', { accountsIds: accountsSelectedRows });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle bulk accounts inactivate.
|
||||||
const handelBulkInactive = () => {
|
const handelBulkInactive = () => {
|
||||||
openAlert('accounts-bulk-inactivate', { accountsIds: accountsSelectedRows });
|
openAlert('accounts-bulk-inactivate', {
|
||||||
|
accountsIds: accountsSelectedRows,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -92,7 +68,7 @@ function AccountsActionsBar({
|
|||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<DashboardActionViewsList
|
<DashboardActionViewsList
|
||||||
resourceName={'accounts'}
|
resourceName={'accounts'}
|
||||||
views={accountsViews}
|
views={resourceViews}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
|
||||||
@@ -104,23 +80,20 @@ function AccountsActionsBar({
|
|||||||
/>
|
/>
|
||||||
<Popover
|
<Popover
|
||||||
minimal={true}
|
minimal={true}
|
||||||
content={filterDropdown}
|
content={''}
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
position={Position.BOTTOM_LEFT}
|
position={Position.BOTTOM_LEFT}
|
||||||
canOutsideClickClose={true}
|
canOutsideClickClose={true}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(Classes.MINIMAL, 'button--filter', {
|
className={classNames(Classes.MINIMAL, 'button--filter', {
|
||||||
'has-active-filters': filterCount > 0,
|
'has-active-filters': false,
|
||||||
})}
|
})}
|
||||||
text={
|
text={
|
||||||
(filterCount <= 0) ? (
|
true ? (
|
||||||
<T id={'filter'} />
|
<T id={'filter'} />
|
||||||
) : (
|
) : (
|
||||||
<T
|
<T id={'count_filters_applied'} values={{ count: 0 }} />
|
||||||
id={'count_filters_applied'}
|
|
||||||
values={{ count: filterCount }}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||||
@@ -169,30 +142,10 @@ function AccountsActionsBar({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Momerize the component.
|
export default compose(
|
||||||
const AccountsActionsBarMemo = memo(AccountsActionsBar);
|
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
|
||||||
resourceName: 'accounts',
|
|
||||||
});
|
|
||||||
|
|
||||||
const withAccountsActionsBar = connect(mapStateToProps);
|
|
||||||
|
|
||||||
const comp = compose(
|
|
||||||
withAccountsActionsBar,
|
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
withAccounts(
|
withAccounts(({ accountsSelectedRows }) => ({
|
||||||
({ accountsSelectedRows, accountsViews, accountsTableQuery }) => ({
|
accountsSelectedRows,
|
||||||
accountsViews,
|
|
||||||
accountsTableQuery,
|
|
||||||
accountsSelectedRows,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
withResourceDetail(({ resourceFields }) => ({
|
|
||||||
resourceFields,
|
|
||||||
})),
|
})),
|
||||||
withAccountsTableActions,
|
withAlertActions,
|
||||||
withAlertActions
|
)(AccountsActionsBar);
|
||||||
)(AccountsActionsBarMemo);
|
|
||||||
|
|
||||||
export default comp;
|
|
||||||
|
|||||||
@@ -1,23 +1,15 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Route, Switch } from 'react-router-dom';
|
import { useIntl } from 'react-intl';
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
import {
|
|
||||||
useIntl,
|
|
||||||
} from 'react-intl';
|
|
||||||
|
|
||||||
import 'style/pages/Accounts/List.scss';
|
import 'style/pages/Accounts/List.scss';
|
||||||
|
|
||||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import { AccountsChartProvider } from 'containers/Accounts/AccountsChartProvider';
|
||||||
import AccountsViewPage from 'containers/Accounts/AccountsViewPage';
|
import AccountsViewPage from 'containers/Accounts/AccountsViewPage';
|
||||||
import AccountsActionsBar from 'containers/Accounts/AccountsActionsBar';
|
import AccountsActionsBar from 'containers/Accounts/AccountsActionsBar';
|
||||||
import AccountsAlerts from './AccountsAlerts';
|
import AccountsAlerts from './AccountsAlerts';
|
||||||
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withResourceActions from 'containers/Resources/withResourcesActions';
|
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
|
||||||
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
|
||||||
import withViewsActions from 'containers/Views/withViewsActions';
|
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
@@ -29,83 +21,29 @@ function AccountsChart({
|
|||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
|
||||||
// #withViewsActions
|
|
||||||
requestFetchResourceViews,
|
|
||||||
|
|
||||||
// #withResourceActions
|
|
||||||
requestFetchResourceFields,
|
|
||||||
|
|
||||||
// #withAccountsTableActions
|
|
||||||
requestFetchAccountsTable,
|
|
||||||
addAccountsTableQueries,
|
|
||||||
|
|
||||||
// #withAccounts
|
// #withAccounts
|
||||||
accountsTableQuery,
|
accountsTableQuery
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
// Fetch accounts resource views and fields.
|
|
||||||
const fetchResourceViews = useQuery(
|
|
||||||
['resource-views', 'accounts'],
|
|
||||||
(key, resourceName) => requestFetchResourceViews(resourceName),
|
|
||||||
);
|
|
||||||
// Fetch the accounts resource fields.
|
|
||||||
const fetchResourceFields = useQuery(
|
|
||||||
['resource-fields', 'accounts'],
|
|
||||||
(key, resourceName) => requestFetchResourceFields(resourceName),
|
|
||||||
);
|
|
||||||
// Fetch accounts list according to the given custom view id.
|
|
||||||
const fetchAccountsHook = useQuery(
|
|
||||||
['accounts-table', accountsTableQuery],
|
|
||||||
(key, q) => requestFetchAccountsTable(),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle(formatMessage({ id: 'chart_of_accounts' }));
|
changePageTitle(formatMessage({ id: 'chart_of_accounts' }));
|
||||||
}, [changePageTitle, formatMessage]);
|
}, [changePageTitle, formatMessage]);
|
||||||
|
|
||||||
// Refetches accounts data table when current custom view changed.
|
|
||||||
const handleFilterChanged = useCallback(() => {
|
|
||||||
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handle fetch data of accounts datatable.
|
|
||||||
const handleFetchData = useCallback(
|
|
||||||
({ pageIndex, pageSize, sortBy }) => {
|
|
||||||
addAccountsTableQueries({
|
|
||||||
...(sortBy.length > 0
|
|
||||||
? {
|
|
||||||
column_sort_by: sortBy[0].id,
|
|
||||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[addAccountsTableQueries],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<AccountsChartProvider query={accountsTableQuery}>
|
||||||
loading={fetchResourceFields.isFetching || fetchResourceViews.isFetching}
|
<AccountsActionsBar />
|
||||||
name={'accounts-chart'}
|
|
||||||
>
|
|
||||||
<AccountsActionsBar
|
|
||||||
onFilterChanged={handleFilterChanged}
|
|
||||||
/>
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
<AccountsViewPage />
|
<AccountsViewPage />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
|
|
||||||
<AccountsAlerts />
|
<AccountsAlerts />
|
||||||
</DashboardInsider>
|
</AccountsChartProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withAccountsActions,
|
|
||||||
withAccountsTableActions,
|
|
||||||
withViewsActions,
|
|
||||||
withResourceActions,
|
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withAccounts(({ accountsTableQuery }) => ({
|
withAccounts(({ accountsTableQuery }) => ({
|
||||||
accountsTableQuery,
|
accountsTableQuery,
|
||||||
|
|||||||
51
client/src/containers/Accounts/AccountsChartProvider.js
Normal file
51
client/src/containers/Accounts/AccountsChartProvider.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import React, { createContext } from 'react';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
import { useResourceViews, useResourceFields, useAccounts } from 'hooks/query';
|
||||||
|
|
||||||
|
const AccountsChartContext = createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts chart data provider.
|
||||||
|
*/
|
||||||
|
function AccountsChartProvider({ query, ...props }) {
|
||||||
|
// Fetch accounts resource views and fields.
|
||||||
|
const { data: resourceViews, isFetching: isViewsLoading } = useResourceViews(
|
||||||
|
'accounts',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetch the accounts resource fields.
|
||||||
|
const {
|
||||||
|
data: resourceFields,
|
||||||
|
isFetching: isFieldsLoading,
|
||||||
|
} = useResourceFields('accounts');
|
||||||
|
|
||||||
|
// Fetch accounts list according to the given custom view id.
|
||||||
|
const { data: accounts, isFetching: isAccountsLoading } = useAccounts(
|
||||||
|
query,
|
||||||
|
{ keepPreviousData: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Provider payload.
|
||||||
|
const provider = {
|
||||||
|
accounts,
|
||||||
|
resourceFields,
|
||||||
|
resourceViews,
|
||||||
|
|
||||||
|
isAccountsLoading,
|
||||||
|
isFieldsLoading,
|
||||||
|
isViewsLoading,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider
|
||||||
|
loading={isViewsLoading || isFieldsLoading}
|
||||||
|
name={'accounts-chart'}
|
||||||
|
>
|
||||||
|
<AccountsChartContext.Provider value={provider} {...props} />
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useAccountsChartContext = () => React.useContext(AccountsChartContext);
|
||||||
|
|
||||||
|
export { AccountsChartProvider, useAccountsChartContext };
|
||||||
@@ -1,98 +1,53 @@
|
|||||||
import React, { useCallback, useState, useMemo, useEffect } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import {
|
import { Button, Popover, Position } from '@blueprintjs/core';
|
||||||
Button,
|
import { useIntl } from 'react-intl';
|
||||||
Popover,
|
|
||||||
Position,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { withRouter } from 'react-router';
|
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Icon, DataTable, If } from 'components';
|
|
||||||
import { saveInvoke, compose } from 'utils';
|
import { Icon, DataTable } from 'components';
|
||||||
import { useUpdateEffect } from 'hooks';
|
import { saveInvoke } from 'utils';
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
import { NormalCell, BalanceCell, AccountActionsMenuList } from './components';
|
import { NormalCell, BalanceCell, AccountActionsMenu } from './components';
|
||||||
import { TableFastCell } from 'components';
|
import { TableFastCell } from 'components';
|
||||||
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
|
|
||||||
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||||
import withCurrentView from 'containers/Views/withCurrentView';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts data-table.
|
* Accounts data-table.
|
||||||
*/
|
*/
|
||||||
function AccountsDataTable({
|
export default function AccountsDataTable({
|
||||||
// #withDashboardActions
|
|
||||||
accountsTable,
|
|
||||||
accountsLoading,
|
|
||||||
|
|
||||||
// #
|
|
||||||
currentViewId,
|
|
||||||
|
|
||||||
// #ownProps
|
// #ownProps
|
||||||
|
accounts,
|
||||||
|
loading,
|
||||||
onFetchData,
|
onFetchData,
|
||||||
onSelectedRowsChange,
|
onSelectedRowsChange,
|
||||||
onDeleteAccount,
|
// onDeleteAccount,
|
||||||
onInactivateAccount,
|
// onInactivateAccount,
|
||||||
onActivateAccount,
|
// onActivateAccount,
|
||||||
onEditAccount,
|
// onEditAccount,
|
||||||
onNewChildAccount
|
// onNewChildAccount,
|
||||||
}) {
|
}) {
|
||||||
const [isMounted, setIsMounted] = useState(false);
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
useEffect(() => {
|
const ActionsCell = useMemo(
|
||||||
setIsMounted(false);
|
() => ({ row }) => (
|
||||||
}, [currentViewId]);
|
|
||||||
|
|
||||||
useUpdateEffect(() => {
|
|
||||||
if (!accountsLoading) {
|
|
||||||
setIsMounted(true);
|
|
||||||
}
|
|
||||||
}, [accountsLoading, setIsMounted]);
|
|
||||||
|
|
||||||
|
|
||||||
const ActionsCell = useMemo(() =>
|
|
||||||
({ row }) => (
|
|
||||||
<Popover
|
<Popover
|
||||||
content={<AccountActionsMenuList
|
content={<AccountActionsMenu row={row} />}
|
||||||
account={row.original}
|
|
||||||
onDeleteAccount={onDeleteAccount}
|
|
||||||
onInactivateAccount={onInactivateAccount}
|
|
||||||
onActivateAccount={onActivateAccount}
|
|
||||||
onEditAccount={onEditAccount}
|
|
||||||
/>}
|
|
||||||
position={Position.RIGHT_TOP}
|
position={Position.RIGHT_TOP}
|
||||||
>
|
>
|
||||||
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
||||||
</Popover>
|
</Popover>
|
||||||
), [
|
),
|
||||||
onDeleteAccount,
|
[],
|
||||||
onInactivateAccount,
|
);
|
||||||
onActivateAccount,
|
|
||||||
onEditAccount
|
|
||||||
]);
|
|
||||||
|
|
||||||
const RowContextMenu = useMemo(() => ({ row }) => (
|
const RowContextMenu = useMemo(
|
||||||
<AccountActionsMenuList
|
() => ({ row }) => <AccountActionsMenu row={row} />,
|
||||||
account={row.original}
|
[],
|
||||||
onDeleteAccount={onDeleteAccount}
|
);
|
||||||
onInactivateAccount={onInactivateAccount}
|
|
||||||
onActivateAccount={onActivateAccount}
|
|
||||||
onEditAccount={onEditAccount}
|
|
||||||
onNewChildAccount={onNewChildAccount}
|
|
||||||
/>
|
|
||||||
), [
|
|
||||||
onDeleteAccount,
|
|
||||||
onInactivateAccount,
|
|
||||||
onActivateAccount,
|
|
||||||
onEditAccount,
|
|
||||||
onNewChildAccount
|
|
||||||
]);
|
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@@ -144,6 +99,7 @@ function AccountsDataTable({
|
|||||||
Cell: ActionsCell,
|
Cell: ActionsCell,
|
||||||
className: 'actions',
|
className: 'actions',
|
||||||
width: 50,
|
width: 50,
|
||||||
|
skeletonWidthMin: 100,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[ActionsCell, formatMessage],
|
[ActionsCell, formatMessage],
|
||||||
@@ -172,13 +128,14 @@ function AccountsDataTable({
|
|||||||
<DataTable
|
<DataTable
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={accountsTable}
|
data={accounts}
|
||||||
onFetchData={handleDatatableFetchData}
|
onFetchData={handleDatatableFetchData}
|
||||||
selectionColumn={true}
|
selectionColumn={true}
|
||||||
expandable={true}
|
expandable={true}
|
||||||
sticky={true}
|
sticky={true}
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
loading={accountsLoading && !isMounted}
|
loading={loading}
|
||||||
|
headerLoading={loading}
|
||||||
rowContextMenu={RowContextMenu}
|
rowContextMenu={RowContextMenu}
|
||||||
rowClassNames={rowClassNames}
|
rowClassNames={rowClassNames}
|
||||||
autoResetExpanded={false}
|
autoResetExpanded={false}
|
||||||
@@ -189,6 +146,8 @@ function AccountsDataTable({
|
|||||||
selectionColumnWidth={50}
|
selectionColumnWidth={50}
|
||||||
TableCellRenderer={TableFastCell}
|
TableCellRenderer={TableFastCell}
|
||||||
TableRowsRenderer={TableVirtualizedListRows}
|
TableRowsRenderer={TableVirtualizedListRows}
|
||||||
|
TableLoadingRenderer={TableSkeletonRows}
|
||||||
|
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||||
// #TableVirtualizedListRows props.
|
// #TableVirtualizedListRows props.
|
||||||
vListrowHeight={42}
|
vListrowHeight={42}
|
||||||
vListOverscanRowCount={10}
|
vListOverscanRowCount={10}
|
||||||
@@ -196,14 +155,3 @@ function AccountsDataTable({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withRouter,
|
|
||||||
withCurrentView,
|
|
||||||
withDashboardActions,
|
|
||||||
withAccountsActions,
|
|
||||||
withAccounts(({ accountsLoading, accountsTable }) => ({
|
|
||||||
accountsLoading,
|
|
||||||
accountsTable,
|
|
||||||
})),
|
|
||||||
)(AccountsDataTable);
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { memo } from 'react';
|
import React from 'react';
|
||||||
import { Switch, Route } from 'react-router-dom';
|
import { Switch, Route } from 'react-router-dom';
|
||||||
import AccountsViewsTabs from 'containers/Accounts/AccountsViewsTabs';
|
import AccountsViewsTabs from 'containers/Accounts/AccountsViewsTabs';
|
||||||
import AccountsDataTable from 'containers/Accounts/AccountsDataTable';
|
import AccountsDataTable from 'containers/Accounts/AccountsDataTable';
|
||||||
|
import { useAccountsChartContext } from 'containers/Accounts/AccountsChartProvider';
|
||||||
|
|
||||||
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
||||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||||
@@ -13,45 +14,50 @@ import { compose } from 'utils';
|
|||||||
* Accounts view page.
|
* Accounts view page.
|
||||||
*/
|
*/
|
||||||
function AccountsViewPage({
|
function AccountsViewPage({
|
||||||
|
// #withAlertActions
|
||||||
openAlert,
|
openAlert,
|
||||||
|
|
||||||
// #withDialog.
|
// #withDialog.
|
||||||
openDialog,
|
openDialog,
|
||||||
|
|
||||||
// #withAccountsTableActions
|
// #withAccountsTableActions
|
||||||
setSelectedRowsAccounts
|
setSelectedRowsAccounts,
|
||||||
}) {
|
}) {
|
||||||
|
const { isAccountsLoading, accounts } = useAccountsChartContext();
|
||||||
|
|
||||||
// Handle delete action account.
|
// Handle delete action account.
|
||||||
const handleDeleteAccount = (account) => {
|
const handleDeleteAccount = (account) => {
|
||||||
openAlert('account-delete', { accountId: account.id })
|
// openAlert('account-delete', { accountId: account.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle activate action account.
|
// Handle activate action account.
|
||||||
const handleActivateAccount = (account) => {
|
const handleActivateAccount = (account) => {
|
||||||
openAlert('account-activate', { accountId: account.id });
|
// openAlert('account-activate', { accountId: account.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle inactivate action account.
|
// Handle inactivate action account.
|
||||||
const handleInactivateAccount = (account) => {
|
const handleInactivateAccount = (account) => {
|
||||||
openAlert('account-inactivate', { accountId: account.id });
|
// openAlert('account-inactivate', { accountId: account.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle select accounts datatable rows.
|
// Handle select accounts datatable rows.
|
||||||
const handleSelectedRowsChange = (selectedRows) => {
|
// const handleSelectedRowsChange = (selectedRows) => {
|
||||||
const selectedRowsIds = selectedRows.map(r => r.id);
|
// const selectedRowsIds = selectedRows.map((r) => r.id);
|
||||||
setSelectedRowsAccounts(selectedRowsIds);
|
// setSelectedRowsAccounts(selectedRowsIds);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// Handle edit account action.
|
||||||
|
const handleEditAccount = (account) => {
|
||||||
|
// openDialog('account-form', { action: 'edit', id: account.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditAccount = (account) => {
|
// Handle new child button click.
|
||||||
openDialog('account-form', { action: 'edit', id: account.id });
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleNewChildAccount = (account) => {
|
const handleNewChildAccount = (account) => {
|
||||||
openDialog('account-form', {
|
// openDialog('account-form', {
|
||||||
action: 'new_child',
|
// action: 'new_child',
|
||||||
parentAccountId: account.id,
|
// parentAccountId: account.id,
|
||||||
accountType: account.account_type,
|
// accountType: account.account_type,
|
||||||
});
|
// });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -63,22 +69,22 @@ function AccountsViewPage({
|
|||||||
<AccountsViewsTabs />
|
<AccountsViewsTabs />
|
||||||
|
|
||||||
<AccountsDataTable
|
<AccountsDataTable
|
||||||
onDeleteAccount={handleDeleteAccount}
|
loading={isAccountsLoading}
|
||||||
onInactivateAccount={handleInactivateAccount}
|
accounts={accounts}
|
||||||
onActivateAccount={handleActivateAccount}
|
// onDeleteAccount={handleDeleteAccount}
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
// onInactivateAccount={handleInactivateAccount}
|
||||||
onEditAccount={handleEditAccount}
|
// onActivateAccount={handleActivateAccount}
|
||||||
onNewChildAccount={handleNewChildAccount}
|
// onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
|
// onEditAccount={handleEditAccount}
|
||||||
|
// onNewChildAccount={handleNewChildAccount}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountsViewPageMemo = memo(AccountsViewPage);
|
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withAlertsActions,
|
withAlertsActions,
|
||||||
withAccountsTableActions,
|
withAccountsTableActions,
|
||||||
withDialogActions
|
withDialogActions,
|
||||||
)(AccountsViewPageMemo);
|
)(AccountsViewPage);
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import React, { useEffect, useMemo, memo, useCallback } from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
import { useHistory } from 'react-router';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
|
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
|
||||||
import { useParams, withRouter } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
import { DashboardViewsTabs } from 'components';
|
import { DashboardViewsTabs } from 'components';
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import { useAccountsChartContext } from 'containers/Accounts/AccountsChartProvider';
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
|
||||||
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
||||||
import withViewDetail from 'containers/Views/withViewDetails';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -17,41 +14,28 @@ import { compose } from 'utils';
|
|||||||
* Accounts views tabs.
|
* Accounts views tabs.
|
||||||
*/
|
*/
|
||||||
function AccountsViewsTabs({
|
function AccountsViewsTabs({
|
||||||
// #withViewDetail
|
|
||||||
viewId,
|
|
||||||
viewItem,
|
|
||||||
|
|
||||||
// #withAccounts
|
|
||||||
accountsViews,
|
|
||||||
|
|
||||||
// #withAccountsTableActions
|
// #withAccountsTableActions
|
||||||
changeAccountsCurrentView,
|
addAccountsTableQuery,
|
||||||
|
|
||||||
// #withDashboardActions
|
|
||||||
setTopbarEditView,
|
|
||||||
changePageSubtitle,
|
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const { resourceViews } = useAccountsChartContext();
|
||||||
const { custom_view_id: customViewId = null } = useParams();
|
const { custom_view_id: customViewId = null } = useParams();
|
||||||
|
|
||||||
useEffect(() => {
|
const handleTabChange = useCallback(
|
||||||
setTopbarEditView(customViewId);
|
(viewId) => {
|
||||||
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
|
addAccountsTableQuery({
|
||||||
}, [customViewId, viewItem, changePageSubtitle, setTopbarEditView]);
|
custom_view_id: viewId || null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[addAccountsTableQuery],
|
||||||
|
);
|
||||||
|
|
||||||
// Handle click a new view tab.
|
const tabs = useMemo(
|
||||||
const handleClickNewView = useCallback(() => {
|
() =>
|
||||||
setTopbarEditView(null);
|
resourceViews.map((view) => ({
|
||||||
history.push('/custom_views/accounts/new');
|
...pick(view, ['name', 'id']),
|
||||||
}, [setTopbarEditView]);
|
})),
|
||||||
|
[resourceViews],
|
||||||
const handleTabChange = useCallback((viewId) => {
|
);
|
||||||
changeAccountsCurrentView(viewId || -1);
|
|
||||||
}, [changeAccountsCurrentView]);
|
|
||||||
|
|
||||||
const tabs = useMemo(() => accountsViews.map((view) => ({
|
|
||||||
...pick(view, ['name', 'id']),
|
|
||||||
})), [accountsViews]);;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar className="navbar--dashboard-views">
|
<Navbar className="navbar--dashboard-views">
|
||||||
@@ -68,21 +52,6 @@ function AccountsViewsTabs({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountsViewsTabsMemo = memo(AccountsViewsTabs);
|
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => ({
|
|
||||||
viewId: -1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const withAccountsViewsTabs = connect(mapStateToProps);
|
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withRouter,
|
|
||||||
withAccountsViewsTabs,
|
|
||||||
withDashboardActions,
|
|
||||||
withAccounts(({ accountsViews }) => ({
|
|
||||||
accountsViews,
|
|
||||||
})),
|
|
||||||
withAccountsTableActions,
|
withAccountsTableActions,
|
||||||
withViewDetail(),
|
)(AccountsViewsTabs);
|
||||||
)(AccountsViewsTabsMemo);
|
|
||||||
|
|||||||
@@ -13,17 +13,8 @@ import classNames from 'classnames';
|
|||||||
import { Icon, Money, If } from 'components';
|
import { Icon, Money, If } from 'components';
|
||||||
import { saveInvoke } from 'utils';
|
import { saveInvoke } from 'utils';
|
||||||
import { formatMessage } from 'services/intl';
|
import { formatMessage } from 'services/intl';
|
||||||
import { POPOVER_CONTENT_SIZING } from '@blueprintjs/core/lib/esm/common/classes';
|
|
||||||
|
|
||||||
export function AccountActionsMenuList({
|
export function AccountActionsMenu({ row: { original } }) {
|
||||||
account,
|
|
||||||
|
|
||||||
onNewChildAccount,
|
|
||||||
onEditAccount,
|
|
||||||
onActivateAccount,
|
|
||||||
onInactivateAccount,
|
|
||||||
onDeleteAccount,
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -34,38 +25,39 @@ export function AccountActionsMenuList({
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<Icon icon="pen-18" />}
|
icon={<Icon icon="pen-18" />}
|
||||||
text={formatMessage({ id: 'edit_account' })}
|
text={formatMessage({ id: 'edit_account' })}
|
||||||
onClick={() => saveInvoke(onEditAccount, account)}
|
// onClick={handleEditAccount}
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<Icon icon="plus" />}
|
icon={<Icon icon="plus" />}
|
||||||
text={formatMessage({ id: 'new_child_account' })}
|
text={formatMessage({ id: 'new_child_account' })}
|
||||||
onClick={() => saveInvoke(onNewChildAccount, account)}
|
// onClick={handleNewChildAccount}
|
||||||
/>
|
/>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<If condition={account.active}>
|
<If condition={original.active}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={formatMessage({ id: 'inactivate_account' })}
|
text={formatMessage({ id: 'inactivate_account' })}
|
||||||
icon={<Icon icon="pause-16" iconSize={16} />}
|
icon={<Icon icon="pause-16" iconSize={16} />}
|
||||||
onClick={() => saveInvoke(onInactivateAccount, account)}
|
// onClick={handleInactivateAccount}
|
||||||
/>
|
/>
|
||||||
</If>
|
</If>
|
||||||
<If condition={!account.active}>
|
<If condition={!original.active}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={formatMessage({ id: 'activate_account' })}
|
text={formatMessage({ id: 'activate_account' })}
|
||||||
icon={<Icon icon="play-16" iconSize={16} />}
|
icon={<Icon icon="play-16" iconSize={16} />}
|
||||||
onClick={() => saveInvoke(onActivateAccount, account)}
|
// onClick={handleActivateAccount}
|
||||||
/>
|
/>
|
||||||
</If>
|
</If>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={formatMessage({ id: 'delete_account' })}
|
text={formatMessage({ id: 'delete_account' })}
|
||||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
onClick={() => saveInvoke(onDeleteAccount, account)}
|
// onClick={handleDeleteA ccount}
|
||||||
/>
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function NormalCell({ cell: { value } }) {
|
export function NormalCell({ cell: { value } }) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const arrowDirection = value === 'credit' ? 'down' : 'up';
|
const arrowDirection = value === 'credit' ? 'down' : 'up';
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ const mapActionsToProps = (dispatch) => ({
|
|||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
}),
|
}),
|
||||||
addAccountsTableQueries: (queries) =>
|
addAccountsTableQuery: (queries) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.ACCOUNTS_TABLE_QUERIES_ADD,
|
type: t.ACCOUNTS_TABLE_QUERIES_ADD,
|
||||||
queries,
|
payload: { queries },
|
||||||
}),
|
}),
|
||||||
setSelectedRowsAccounts: (selectedRows) =>
|
setSelectedRowsAccounts: (selectedRows) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
|
||||||
|
|
||||||
|
import { useActivateAccount } from 'hooks/query';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account activate alert.
|
* Account activate alert.
|
||||||
*/
|
*/
|
||||||
@@ -20,11 +18,12 @@ function AccountActivateAlert({
|
|||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
|
|
||||||
requestActivateAccount,
|
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
mutateAsync: activateAccount,
|
||||||
|
isLoading
|
||||||
|
} = useActivateAccount();
|
||||||
|
|
||||||
// Handle alert cancel.
|
// Handle alert cancel.
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
@@ -33,22 +32,17 @@ function AccountActivateAlert({
|
|||||||
|
|
||||||
// Handle activate account confirm.
|
// Handle activate account confirm.
|
||||||
const handleConfirmAccountActivate = () => {
|
const handleConfirmAccountActivate = () => {
|
||||||
setLoading(true);
|
activateAccount(accountId).then(() => {
|
||||||
requestActivateAccount(accountId)
|
AppToaster.show({
|
||||||
.then(() => {
|
message: formatMessage({
|
||||||
AppToaster.show({
|
id: 'the_account_has_been_successfully_activated',
|
||||||
message: formatMessage({
|
}),
|
||||||
id: 'the_account_has_been_successfully_activated',
|
intent: Intent.SUCCESS,
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
queryCache.invalidateQueries('accounts-table');
|
|
||||||
})
|
|
||||||
.catch((error) => {})
|
|
||||||
.finally(() => {
|
|
||||||
closeAlert('account-activate');
|
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
|
closeAlert('account-activate');
|
||||||
|
}).finally(() => {
|
||||||
|
closeAlert('account-activate');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -71,5 +65,4 @@ function AccountActivateAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withAccountsActions,
|
|
||||||
)(AccountActivateAlert);
|
)(AccountActivateAlert);
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
FormattedHTMLMessage,
|
FormattedHTMLMessage,
|
||||||
useIntl,
|
useIntl,
|
||||||
} from 'react-intl';
|
} from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import { handleDeleteErrors } from 'containers/Accounts/utils';
|
import { handleDeleteErrors } from 'containers/Accounts/utils';
|
||||||
|
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { useDeleteAccount } from 'hooks/query';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,40 +26,32 @@ function AccountDeleteAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { accountId },
|
payload: { accountId },
|
||||||
|
|
||||||
// #withAccountsActions
|
|
||||||
requestDeleteAccount,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
isLoading,
|
||||||
|
mutateAsync: deleteAccount,
|
||||||
|
} = useDeleteAccount();
|
||||||
|
|
||||||
// handle cancel delete account alert.
|
// handle cancel delete account alert.
|
||||||
const handleCancelAccountDelete = () => {
|
const handleCancelAccountDelete = () => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle confirm account delete.
|
// Handle confirm account delete.
|
||||||
const handleConfirmAccountDelete = () => {
|
const handleConfirmAccountDelete = () => {
|
||||||
setLoading(true);
|
deleteAccount(accountId).then(() => {
|
||||||
requestDeleteAccount(accountId)
|
AppToaster.show({
|
||||||
.then(() => {
|
message: formatMessage({
|
||||||
AppToaster.show({
|
id: 'the_account_has_been_successfully_deleted',
|
||||||
message: formatMessage({
|
}),
|
||||||
id: 'the_account_has_been_successfully_deleted',
|
intent: Intent.SUCCESS,
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
queryCache.invalidateQueries('accounts-table');
|
|
||||||
})
|
|
||||||
.catch((errors) => {
|
|
||||||
handleDeleteErrors(errors);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false);
|
|
||||||
closeAlert(name);
|
|
||||||
});
|
});
|
||||||
|
closeAlert(name);
|
||||||
|
}).catch(errors => {
|
||||||
|
handleDeleteErrors(errors);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -85,5 +77,4 @@ function AccountDeleteAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withAccountsActions,
|
|
||||||
)(AccountDeleteAlert);
|
)(AccountDeleteAlert);
|
||||||
|
|||||||
@@ -1,50 +1,50 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
import { useInactivateAccount } from 'hooks/query';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account inactivate alert.
|
||||||
|
*/
|
||||||
function AccountInactivateAlert({
|
function AccountInactivateAlert({
|
||||||
name,
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
isOpen,
|
isOpen,
|
||||||
payload: { accountId },
|
payload: { accountId },
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
|
|
||||||
// #withAccountsActions
|
|
||||||
requestInactiveAccount,
|
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
mutateAsync: inactivateAccount,
|
||||||
|
isLoading
|
||||||
|
} = useInactivateAccount();
|
||||||
|
|
||||||
const handleCancelInactiveAccount = () => {
|
const handleCancelInactiveAccount = () => {
|
||||||
closeAlert('account-inactivate');
|
closeAlert('account-inactivate');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirmAccountActive = () => {
|
const handleConfirmAccountActive = () => {
|
||||||
setLoading(true);
|
inactivateAccount(accountId).then(() => {
|
||||||
requestInactiveAccount(accountId)
|
AppToaster.show({
|
||||||
.then(() => {
|
message: formatMessage({
|
||||||
AppToaster.show({
|
id: 'the_account_has_been_successfully_inactivated',
|
||||||
message: formatMessage({
|
}),
|
||||||
id: 'the_account_has_been_successfully_inactivated',
|
intent: Intent.SUCCESS,
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
queryCache.invalidateQueries('accounts-table');
|
|
||||||
})
|
|
||||||
.catch((error) => {})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false);
|
|
||||||
closeAlert('account-inactivate');
|
|
||||||
});
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
|
||||||
|
}).finally(() => {
|
||||||
|
closeAlert('account-inactivate');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -67,5 +67,4 @@ function AccountInactivateAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withAccountsActions,
|
|
||||||
)(AccountInactivateAlert);
|
)(AccountInactivateAlert);
|
||||||
|
|||||||
70
client/src/containers/Alerts/Bills/BillDeleteAlert.js
Normal file
70
client/src/containers/Alerts/Bills/BillDeleteAlert.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
useIntl,
|
||||||
|
} from 'react-intl';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { useDeleteBill } from 'hooks/query';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bill delete alert.
|
||||||
|
*/
|
||||||
|
function BillDeleteAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { billId },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const { isLoading, mutateAsync: deleteBillMutate } = useDeleteBill();
|
||||||
|
|
||||||
|
// handle cancel Bill
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// handleConfirm delete invoice
|
||||||
|
const handleConfirmBillDelete = () => {
|
||||||
|
deleteBillMutate(billId).then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_bill_has_been_deleted_successfully',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'delete'} />}
|
||||||
|
icon={'trash'}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirmBillDelete}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_this_bill_you_will_able_to_restore_it'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(BillDeleteAlert);
|
||||||
69
client/src/containers/Alerts/Bills/BillOpenAlert.js
Normal file
69
client/src/containers/Alerts/Bills/BillOpenAlert.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { useOpenBill } from 'hooks/query';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bill open alert.
|
||||||
|
*/
|
||||||
|
function BillOpenAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { billId },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const { isLoading, mutateAsync: openBillMutate } = useOpenBill();
|
||||||
|
|
||||||
|
// Handle cancel open bill alert.
|
||||||
|
const handleCancelOpenBill = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle confirm bill open.
|
||||||
|
const handleConfirmBillOpen = () => {
|
||||||
|
openBillMutate(billId)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_bill_has_been_opened_successfully',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally((error) => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'open'} />}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelOpenBill}
|
||||||
|
onConfirm={handleConfirmBillOpen}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'are_sure_to_open_this_bill'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(BillOpenAlert);
|
||||||
@@ -13,8 +13,10 @@ import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
|||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
import withCustomersActions from 'containers/Customers/withCustomersActions';
|
import withCustomersActions from 'containers/Customers/withCustomersActions';
|
||||||
|
|
||||||
|
import { useDeleteCustomer } from 'hooks/query';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customer delete alert.
|
* Customer delete alert.
|
||||||
*/
|
*/
|
||||||
@@ -31,7 +33,10 @@ function CustomerDeleteAlert({
|
|||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
mutateAsync: deleteCustomerMutate,
|
||||||
|
isLoading
|
||||||
|
} = useDeleteCustomer();
|
||||||
|
|
||||||
// handle cancel delete alert.
|
// handle cancel delete alert.
|
||||||
const handleCancelDeleteAlert = () => {
|
const handleCancelDeleteAlert = () => {
|
||||||
@@ -40,8 +45,7 @@ function CustomerDeleteAlert({
|
|||||||
|
|
||||||
// handle confirm delete customer.
|
// handle confirm delete customer.
|
||||||
const handleConfirmDeleteCustomer = useCallback(() => {
|
const handleConfirmDeleteCustomer = useCallback(() => {
|
||||||
setLoading(true);
|
deleteCustomerMutate(customerId)
|
||||||
requestDeleteCustomer(customerId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -55,10 +59,9 @@ function CustomerDeleteAlert({
|
|||||||
transformErrors(errors);
|
transformErrors(errors);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
});
|
});
|
||||||
}, [requestDeleteCustomer, customerId, formatMessage]);
|
}, [deleteCustomerMutate, customerId, closeAlert, name, formatMessage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
import { queryCache } from 'react-query';
|
||||||
|
|
||||||
|
import { useApproveEstimate } from 'hooks/query';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
import withEstimateActions from 'containers/Sales/Estimate/withEstimateActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimate Approve alert.
|
* Estimate approve alert.
|
||||||
*/
|
*/
|
||||||
function EstimateApproveAlert({
|
function EstimateApproveAlert({
|
||||||
name,
|
name,
|
||||||
@@ -27,7 +28,10 @@ function EstimateApproveAlert({
|
|||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
mutateAsync: deliverEstimateMutate,
|
||||||
|
isLoading,
|
||||||
|
} = useApproveEstimate();
|
||||||
|
|
||||||
// handle cancel approve alert.
|
// handle cancel approve alert.
|
||||||
const handleCancelApproveEstimate = () => {
|
const handleCancelApproveEstimate = () => {
|
||||||
@@ -35,8 +39,7 @@ function EstimateApproveAlert({
|
|||||||
};
|
};
|
||||||
// Handle confirm estimate approve.
|
// Handle confirm estimate approve.
|
||||||
const handleConfirmEstimateApprove = useCallback(() => {
|
const handleConfirmEstimateApprove = useCallback(() => {
|
||||||
setLoading(true);
|
deliverEstimateMutate(estimateId)
|
||||||
requestApproveEstimate(estimateId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -48,10 +51,9 @@ function EstimateApproveAlert({
|
|||||||
})
|
})
|
||||||
.catch((error) => {})
|
.catch((error) => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
});
|
});
|
||||||
}, [estimateId, requestApproveEstimate, formatMessage]);
|
}, [estimateId, deliverEstimateMutate, closeAlert, name, formatMessage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -74,5 +76,4 @@ function EstimateApproveAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withEstimateActions,
|
|
||||||
)(EstimateApproveAlert);
|
)(EstimateApproveAlert);
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import {
|
|||||||
useIntl,
|
useIntl,
|
||||||
} from 'react-intl';
|
} from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
import { useDeleteEstimate } from 'hooks/query';
|
||||||
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
import withEstimateActions from 'containers/Sales/Estimate/withEstimateActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -24,14 +24,11 @@ function EstimateDeleteAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { estimateId },
|
payload: { estimateId },
|
||||||
|
|
||||||
// #withEstimateActions
|
|
||||||
requestDeleteEstimate,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const { mutateAsync: deleteEstimateMutate, isLoading } = useDeleteEstimate();
|
||||||
|
|
||||||
// handle cancel delete alert.
|
// handle cancel delete alert.
|
||||||
const handleCancelEstimateDelete = () => {
|
const handleCancelEstimateDelete = () => {
|
||||||
@@ -40,8 +37,7 @@ function EstimateDeleteAlert({
|
|||||||
|
|
||||||
// handle confirm delete estimate
|
// handle confirm delete estimate
|
||||||
const handleConfirmEstimateDelete = useCallback(() => {
|
const handleConfirmEstimateDelete = useCallback(() => {
|
||||||
setLoading(true);
|
deleteEstimateMutate(estimateId)
|
||||||
requestDeleteEstimate(estimateId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -49,14 +45,12 @@ function EstimateDeleteAlert({
|
|||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.invalidateQueries('estimates-table');
|
|
||||||
})
|
})
|
||||||
.catch(({ errors }) => {})
|
.catch(({ errors }) => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
});
|
});
|
||||||
}, [requestDeleteEstimate, formatMessage, estimateId]);
|
}, [deleteEstimateMutate, name, closeAlert, formatMessage, estimateId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -81,5 +75,4 @@ function EstimateDeleteAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withEstimateActions,
|
|
||||||
)(EstimateDeleteAlert);
|
)(EstimateDeleteAlert);
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import React, { useCallback, useState } from 'react';
|
|||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
import { queryCache } from 'react-query';
|
||||||
|
|
||||||
|
import { useDeliverEstimate } from 'hooks/query';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
import withEstimateActions from 'containers/Sales/Estimate/withEstimateActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -20,14 +21,11 @@ function EstimateDeliveredAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { estimateId },
|
payload: { estimateId },
|
||||||
|
|
||||||
// #withEstimateActions
|
|
||||||
requestDeliveredEstimate,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const { mutateAsync: deliverEstimateMutate, isLoading } = useDeliverEstimate();
|
||||||
|
|
||||||
// Handle cancel delivered estimate alert.
|
// Handle cancel delivered estimate alert.
|
||||||
const handleCancelDeliveredEstimate = () => {
|
const handleCancelDeliveredEstimate = () => {
|
||||||
@@ -36,8 +34,7 @@ function EstimateDeliveredAlert({
|
|||||||
|
|
||||||
// Handle confirm estimate delivered.
|
// Handle confirm estimate delivered.
|
||||||
const handleConfirmEstimateDelivered = useCallback(() => {
|
const handleConfirmEstimateDelivered = useCallback(() => {
|
||||||
setLoading(true);
|
deliverEstimateMutate(estimateId)
|
||||||
requestDeliveredEstimate(estimateId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -50,9 +47,8 @@ function EstimateDeliveredAlert({
|
|||||||
.catch((error) => {})
|
.catch((error) => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
}, [estimateId, requestDeliveredEstimate, formatMessage]);
|
}, [estimateId, deliverEstimateMutate, formatMessage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -74,5 +70,4 @@ function EstimateDeliveredAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withEstimateActions,
|
|
||||||
)(EstimateDeliveredAlert);
|
)(EstimateDeliveredAlert);
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
import { queryCache } from 'react-query';
|
||||||
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
import { useRejectEstimate } from 'hooks/query';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
@@ -27,7 +29,11 @@ function EstimateRejectAlert({
|
|||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
mutateAsync: rejectEstimateMutate,
|
||||||
|
isLoading
|
||||||
|
} = useRejectEstimate();
|
||||||
|
|
||||||
// Handle cancel reject estimate alert.
|
// Handle cancel reject estimate alert.
|
||||||
const handleCancelRejectEstimate = () => {
|
const handleCancelRejectEstimate = () => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
@@ -35,7 +41,6 @@ function EstimateRejectAlert({
|
|||||||
|
|
||||||
// Handle confirm estimate reject.
|
// Handle confirm estimate reject.
|
||||||
const handleConfirmEstimateReject = useCallback(() => {
|
const handleConfirmEstimateReject = useCallback(() => {
|
||||||
setLoading(true);
|
|
||||||
requestRejectEstimate(estimateId)
|
requestRejectEstimate(estimateId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
@@ -48,10 +53,9 @@ function EstimateRejectAlert({
|
|||||||
})
|
})
|
||||||
.catch((error) => {})
|
.catch((error) => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
});
|
});
|
||||||
}, [estimateId, requestRejectEstimate, formatMessage]);
|
}, [estimateId, rejectEstimateMutate, formatMessage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { usePublishExpense } from 'hooks/query';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expense bulk delete alert.
|
||||||
|
*/
|
||||||
|
function ExpenseBulkDeleteAlert({
|
||||||
|
closeAlert,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
name,
|
||||||
|
payload: { expenseId, selectedCount },
|
||||||
|
isOpen,
|
||||||
|
}) {
|
||||||
|
// Handle confirm journals bulk delete.
|
||||||
|
const handleConfirmBulkDelete = () => {
|
||||||
|
// requestDeleteBulkExpenses(bulkDelete)
|
||||||
|
// .then(() => {
|
||||||
|
// AppToaster.show({
|
||||||
|
// message: formatMessage(
|
||||||
|
// { id: 'the_expenses_have_been_deleted_successfully' },
|
||||||
|
// { count: selectedRowsCount },
|
||||||
|
// ),
|
||||||
|
// intent: Intent.SUCCESS,
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle cancel bulk delete alert.
|
||||||
|
const handleCancelBulkDelete = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={
|
||||||
|
<T id={'delete_count'} values={{ count: selectedCount }} />
|
||||||
|
}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelBulkDelete}
|
||||||
|
onConfirm={handleConfirmBulkDelete}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_these_expenses_you_will_not_able_restore_them'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(ExpenseBulkDeleteAlert);
|
||||||
71
client/src/containers/Alerts/Expenses/ExpenseDeleteAlert.js
Normal file
71
client/src/containers/Alerts/Expenses/ExpenseDeleteAlert.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { useDeleteExpense } from 'hooks/query';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expense delete alert.
|
||||||
|
*/
|
||||||
|
function ExpenseDeleteAlert({
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { expenseId },
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const {
|
||||||
|
mutateAsync: deleteExpenseMutate,
|
||||||
|
isLoading,
|
||||||
|
deleteExpense
|
||||||
|
} = useDeleteExpense();
|
||||||
|
|
||||||
|
// Handle cancel expense journal.
|
||||||
|
const handleCancelExpenseDelete = () => {
|
||||||
|
closeAlert('expense-delete');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle confirm delete expense.
|
||||||
|
const handleConfirmExpenseDelete = () => {
|
||||||
|
deleteExpenseMutate(expenseId).then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage(
|
||||||
|
{ id: 'the_expense_has_been_deleted_successfully' },
|
||||||
|
{ number: expenseId },
|
||||||
|
),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
}).finally(() => {
|
||||||
|
closeAlert('expense-delete');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'delete'} />}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelExpenseDelete}
|
||||||
|
onConfirm={handleConfirmExpenseDelete}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_this_expense_you_will_able_to_restore_it'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(ExpenseDeleteAlert);
|
||||||
64
client/src/containers/Alerts/Expenses/ExpensePublishAlert.js
Normal file
64
client/src/containers/Alerts/Expenses/ExpensePublishAlert.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { usePublishExpense } from 'hooks/query';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expense publish alert.
|
||||||
|
*/
|
||||||
|
function ExpensePublishAlert({
|
||||||
|
closeAlert,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
name,
|
||||||
|
payload: { expenseId },
|
||||||
|
isOpen,
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const { mutateAsync: publishExpenseMutate, isLoading } = usePublishExpense();
|
||||||
|
|
||||||
|
const handleCancelPublishExpense = () => {
|
||||||
|
closeAlert('expense-publish');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle publish expense confirm.
|
||||||
|
const handleConfirmPublishExpense = () => {
|
||||||
|
publishExpenseMutate(expenseId)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_expense_has_been_published',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'publish'} />}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelPublishExpense}
|
||||||
|
onConfirm={handleConfirmPublishExpense}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'are_sure_to_publish_this_expense'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(ExpensePublishAlert);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
FormattedHTMLMessage,
|
FormattedHTMLMessage,
|
||||||
@@ -6,7 +6,9 @@ import {
|
|||||||
} from 'react-intl';
|
} from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
import { queryCache } from 'react-query';
|
||||||
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
import { useDeleteInvoice } from 'hooks/query';
|
||||||
|
|
||||||
import { handleDeleteErrors } from 'containers/Sales/Invoice/components';
|
import { handleDeleteErrors } from 'containers/Sales/Invoice/components';
|
||||||
|
|
||||||
@@ -26,14 +28,14 @@ function InvoiceDeleteAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { invoiceId },
|
payload: { invoiceId },
|
||||||
|
|
||||||
// #withInvoiceActions
|
|
||||||
requestDeleteInvoice,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
mutateAsync: deleteInvoiceMutate,
|
||||||
|
isLoading
|
||||||
|
} = useDeleteInvoice();
|
||||||
|
|
||||||
// handle cancel delete invoice alert.
|
// handle cancel delete invoice alert.
|
||||||
const handleCancelDeleteAlert = () => {
|
const handleCancelDeleteAlert = () => {
|
||||||
@@ -41,9 +43,8 @@ function InvoiceDeleteAlert({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// handleConfirm delete invoice
|
// handleConfirm delete invoice
|
||||||
const handleConfirmInvoiceDelete = useCallback(() => {
|
const handleConfirmInvoiceDelete = () => {
|
||||||
setLoading(true);
|
deleteInvoiceMutate(invoiceId)
|
||||||
requestDeleteInvoice(invoiceId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -51,16 +52,14 @@ function InvoiceDeleteAlert({
|
|||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.invalidateQueries('invoices-table');
|
|
||||||
})
|
})
|
||||||
.catch((errors) => {
|
.catch((errors) => {
|
||||||
handleDeleteErrors(errors);
|
handleDeleteErrors(errors);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
}, [invoiceId, requestDeleteInvoice, formatMessage]);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
|
||||||
|
import { useDeliverInvoice } from 'hooks/query';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
@@ -11,7 +12,7 @@ import withInvoiceActions from 'containers/Sales/Invoice/withInvoiceActions';
|
|||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoice alert.
|
* Sale invoice alert.
|
||||||
*/
|
*/
|
||||||
function InvoiceDeliverAlert({
|
function InvoiceDeliverAlert({
|
||||||
name,
|
name,
|
||||||
@@ -20,14 +21,14 @@ function InvoiceDeliverAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { invoiceId },
|
payload: { invoiceId },
|
||||||
|
|
||||||
// #withInvoiceActions
|
|
||||||
requestDeliverInvoice,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
mutateAsync: deliverInvoiceMutate,
|
||||||
|
isLoading
|
||||||
|
} = useDeliverInvoice();
|
||||||
|
|
||||||
// handle cancel delete deliver alert.
|
// handle cancel delete deliver alert.
|
||||||
const handleCancelDeleteAlert = () => {
|
const handleCancelDeleteAlert = () => {
|
||||||
@@ -35,9 +36,8 @@ function InvoiceDeliverAlert({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle confirm invoice deliver.
|
// Handle confirm invoice deliver.
|
||||||
const handleConfirmInvoiceDeliver = useCallback(() => {
|
const handleConfirmInvoiceDeliver = () => {
|
||||||
setLoading(true);
|
deliverInvoiceMutate(invoiceId)
|
||||||
requestDeliverInvoice(invoiceId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -45,14 +45,12 @@ function InvoiceDeliverAlert({
|
|||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.invalidateQueries('invoices-table');
|
|
||||||
})
|
})
|
||||||
.catch((error) => {})
|
.catch((error) => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
}, [invoiceId, requestDeliverInvoice, formatMessage]);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
FormattedHTMLMessage,
|
FormattedHTMLMessage,
|
||||||
useIntl,
|
useIntl,
|
||||||
} from 'react-intl';
|
} from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
import withInventoryAdjustmentActions from 'containers/Items/withInventoryAdjustmentActions';
|
import {
|
||||||
|
useDeleteInventoryAdjustment
|
||||||
|
} from 'hooks/query';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -23,23 +24,24 @@ function InventoryAdjustmentDeleteAlert({
|
|||||||
// #withAlertStoreConnect
|
// #withAlertStoreConnect
|
||||||
isOpen,
|
isOpen,
|
||||||
payload: { inventoryId },
|
payload: { inventoryId },
|
||||||
// #withInventoryAdjustmentActions
|
|
||||||
requestDeleteInventoryAdjustment,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
mutateAsync: deleteInventoryAdjMutate,
|
||||||
|
isLoading
|
||||||
|
} = useDeleteInventoryAdjustment();
|
||||||
|
|
||||||
// handle cancel delete alert.
|
// handle cancel delete alert.
|
||||||
const handleCancelInventoryAdjustmentDelete = () => {
|
const handleCancelInventoryAdjustmentDelete = () => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle the confirm delete of the inventory adjustment transaction.
|
||||||
const handleConfirmInventoryAdjustmentDelete = () => {
|
const handleConfirmInventoryAdjustmentDelete = () => {
|
||||||
setLoading(true);
|
deleteInventoryAdjMutate(inventoryId)
|
||||||
requestDeleteInventoryAdjustment(inventoryId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -47,11 +49,9 @@ function InventoryAdjustmentDeleteAlert({
|
|||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.invalidateQueries('inventory-adjustment-list');
|
|
||||||
})
|
})
|
||||||
.catch((errors) => {})
|
.catch((errors) => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -81,5 +81,4 @@ function InventoryAdjustmentDeleteAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withInventoryAdjustmentActions,
|
|
||||||
)(InventoryAdjustmentDeleteAlert);
|
)(InventoryAdjustmentDeleteAlert);
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withItemsActions from 'containers/Items/withItemsActions';
|
import {
|
||||||
|
useActivateItem,
|
||||||
|
} from 'hooks/query';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
@@ -20,14 +22,11 @@ function ItemActivateAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { itemId },
|
payload: { itemId },
|
||||||
|
|
||||||
// #withItemsActions
|
|
||||||
requestActivateItem,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const { mutateAsync: activateItem, isLoading } = useActivateItem();
|
||||||
|
|
||||||
// Handle activate item alert cancel.
|
// Handle activate item alert cancel.
|
||||||
const handleCancelActivateItem = () => {
|
const handleCancelActivateItem = () => {
|
||||||
@@ -36,8 +35,7 @@ function ItemActivateAlert({
|
|||||||
|
|
||||||
// Handle confirm item activated.
|
// Handle confirm item activated.
|
||||||
const handleConfirmItemActivate = () => {
|
const handleConfirmItemActivate = () => {
|
||||||
setLoading(true);
|
activateItem(itemId)
|
||||||
requestActivateItem(itemId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -45,12 +43,10 @@ function ItemActivateAlert({
|
|||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.invalidateQueries('items-table');
|
|
||||||
})
|
})
|
||||||
.catch((error) => {})
|
.catch((error) => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -61,8 +57,8 @@ function ItemActivateAlert({
|
|||||||
intent={Intent.WARNING}
|
intent={Intent.WARNING}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onCancel={handleCancelActivateItem}
|
onCancel={handleCancelActivateItem}
|
||||||
onConfirm={handleConfirmItemActivate}
|
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
onConfirm={handleConfirmItemActivate}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
<T id={'are_sure_to_activate_this_item'} />
|
<T id={'are_sure_to_activate_this_item'} />
|
||||||
@@ -74,5 +70,4 @@ function ItemActivateAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withItemsActions,
|
|
||||||
)(ItemActivateAlert);
|
)(ItemActivateAlert);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { size } from 'lodash';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withItemsActions from 'containers/Items/withItemsActions';
|
import withItemsActions from 'containers/Items/withItemsActions';
|
||||||
@@ -54,7 +55,7 @@ function ItemBulkDeleteAlert({
|
|||||||
<Alert
|
<Alert
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
confirmButtonText={
|
confirmButtonText={
|
||||||
<T id={'delete_count'} values={{ count: itemsIds.length }} />
|
<T id={'delete_count'} values={{ count: size(itemsIds) }} />
|
||||||
}
|
}
|
||||||
icon="trash"
|
icon="trash"
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
useIntl,
|
useIntl,
|
||||||
} from 'react-intl';
|
} from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { size } from 'lodash';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
|
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
|
||||||
@@ -59,7 +60,7 @@ function ItemCategoryBulkDeleteAlert({
|
|||||||
<Alert
|
<Alert
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
confirmButtonText={
|
confirmButtonText={
|
||||||
<T id={'delete_count'} values={{ count: itemCategoriesIds.length }} />
|
<T id={'delete_count'} values={{ count: size(itemCategoriesIds) }} />
|
||||||
}
|
}
|
||||||
icon="trash"
|
icon="trash"
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
FormattedHTMLMessage,
|
FormattedHTMLMessage,
|
||||||
useIntl,
|
useIntl,
|
||||||
} from 'react-intl';
|
} from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { AppToaster } from 'components';
|
|
||||||
import { queryCache } from 'react-query';
|
|
||||||
|
|
||||||
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
|
import { useDeleteItemCategory } from 'hooks/query';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
@@ -24,14 +24,14 @@ function ItemCategoryDeleteAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { itemCategoryId },
|
payload: { itemCategoryId },
|
||||||
|
|
||||||
// #withItemCategoriesActions
|
|
||||||
requestDeleteItemCategory,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
mutateAsync: deleteItemCategory,
|
||||||
|
isLoading,
|
||||||
|
} = useDeleteItemCategory();
|
||||||
|
|
||||||
// handle cancel delete item category alert.
|
// handle cancel delete item category alert.
|
||||||
const handleCancelItemCategoryDelete = () => {
|
const handleCancelItemCategoryDelete = () => {
|
||||||
@@ -40,8 +40,7 @@ function ItemCategoryDeleteAlert({
|
|||||||
|
|
||||||
// Handle alert confirm delete item category.
|
// Handle alert confirm delete item category.
|
||||||
const handleConfirmItemDelete = () => {
|
const handleConfirmItemDelete = () => {
|
||||||
setLoading(true);
|
deleteItemCategory(itemCategoryId)
|
||||||
requestDeleteItemCategory(itemCategoryId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -49,11 +48,9 @@ function ItemCategoryDeleteAlert({
|
|||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.invalidateQueries('items-categories-list');
|
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -81,5 +78,4 @@ function ItemCategoryDeleteAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withItemCategoriesActions,
|
|
||||||
)(ItemCategoryDeleteAlert);
|
)(ItemCategoryDeleteAlert);
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import { AppToaster } from 'components';
|
|||||||
|
|
||||||
import { handleDeleteErrors } from 'containers/Items/utils';
|
import { handleDeleteErrors } from 'containers/Items/utils';
|
||||||
|
|
||||||
import withItemsActions from 'containers/Items/withItemsActions';
|
import {
|
||||||
|
useDeleteItem
|
||||||
|
} from 'hooks/query';
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
@@ -26,14 +28,11 @@ function ItemDeleteAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { itemId },
|
payload: { itemId },
|
||||||
|
|
||||||
// #withItemsActions
|
|
||||||
requestDeleteItem,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
|
const { mutateAsync: deleteItem, isLoading } = useDeleteItem();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
// handle cancel delete item alert.
|
// handle cancel delete item alert.
|
||||||
const handleCancelItemDelete = () => {
|
const handleCancelItemDelete = () => {
|
||||||
@@ -41,8 +40,7 @@ function ItemDeleteAlert({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirmDeleteItem = () => {
|
const handleConfirmDeleteItem = () => {
|
||||||
setLoading(true);
|
deleteItem(itemId)
|
||||||
requestDeleteItem(itemId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -50,14 +48,12 @@ function ItemDeleteAlert({
|
|||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.invalidateQueries('items-table');
|
|
||||||
})
|
})
|
||||||
.catch(({ errors }) => {
|
.catch(({ errors }) => {
|
||||||
handleDeleteErrors(errors);
|
handleDeleteErrors(errors);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,5 +80,4 @@ function ItemDeleteAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withItemsActions,
|
|
||||||
)(ItemDeleteAlert);
|
)(ItemDeleteAlert);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withItemsActions from 'containers/Items/withItemsActions';
|
import { useInactivateItem } from 'hooks/query';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
@@ -20,24 +20,20 @@ function ItemInactivateAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { itemId },
|
payload: { itemId },
|
||||||
|
|
||||||
// #withItemsActions
|
|
||||||
requestInactiveItem,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const { mutateAsync: inactivateItem, isLoading } = useInactivateItem();
|
||||||
|
|
||||||
// handle cancel inactivate alert.
|
// Handle cancel inactivate alert.
|
||||||
const handleCancelInactivateItem = () => {
|
const handleCancelInactivateItem = () => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle confirm item Inactive.
|
// Handle confirm item Inactive.
|
||||||
const handleConfirmItemInactive = () => {
|
const handleConfirmItemInactive = () => {
|
||||||
setLoading(true);
|
inactivateItem(itemId)
|
||||||
requestInactiveItem(itemId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -45,11 +41,9 @@ function ItemInactivateAlert({
|
|||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.invalidateQueries('items-table');
|
|
||||||
})
|
})
|
||||||
.catch((error) => {})
|
.catch((error) => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -74,5 +68,4 @@ function ItemInactivateAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withItemsActions,
|
|
||||||
)(ItemInactivateAlert);
|
)(ItemInactivateAlert);
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function JournalBulkDeleteAlert({}) {
|
||||||
|
// Handle confirm journals bulk delete.
|
||||||
|
const handleConfirmBulkDelete = useCallback(() => {
|
||||||
|
requestDeleteBulkManualJournals(bulkDelete)
|
||||||
|
.then(() => {
|
||||||
|
setBulkDelete(false);
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage(
|
||||||
|
{ id: 'the_journals_has_been_deleted_successfully' },
|
||||||
|
{ count: selectedRowsCount },
|
||||||
|
),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setBulkDelete(false);
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
requestDeleteBulkManualJournals,
|
||||||
|
bulkDelete,
|
||||||
|
formatMessage,
|
||||||
|
selectedRowsCount,
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={
|
||||||
|
<T id={'delete_count'} values={{ count: selectedRowsCount }} />
|
||||||
|
}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={bulkDelete}
|
||||||
|
onCancel={handleCancelBulkDelete}
|
||||||
|
onConfirm={handleConfirmBulkDelete}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_these_journals_you_will_not_able_restore_them'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import { useDeleteJournal } from 'hooks/query';
|
||||||
|
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Journal delete alert.
|
||||||
|
*/
|
||||||
|
function JournalDeleteAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { manualJournalId, journalNumber },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const { mutate: deleteJournalMutate } = useDeleteJournal();
|
||||||
|
|
||||||
|
// Handle cancel delete manual journal.
|
||||||
|
const handleCancelAlert = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle confirm delete manual journal.
|
||||||
|
const handleConfirmManualJournalDelete = () => {
|
||||||
|
deleteJournalMutate(manualJournalId).then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage(
|
||||||
|
{ id: 'the_journal_has_been_deleted_successfully' },
|
||||||
|
{ number: journalNumber },
|
||||||
|
),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'delete'} />}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelAlert}
|
||||||
|
onConfirm={handleConfirmManualJournalDelete}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_this_journal_you_will_able_to_restore_it'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(JournalDeleteAlert);
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import { usePublishJournal } from 'hooks/query';
|
||||||
|
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Journal publish alert.
|
||||||
|
*/
|
||||||
|
function JournalPublishAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { manualJournalId, journalNumber },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const { mutate: publishJournalMutate, isLoading } = usePublishJournal();
|
||||||
|
|
||||||
|
// Handle cancel manual journal alert.
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle publish manual journal confirm.
|
||||||
|
const handleConfirm = () => {
|
||||||
|
publishJournalMutate(manualJournalId)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_manual_journal_has_been_published',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'publish'} />}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'are_sure_to_publish_this_manual_journal'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(JournalPublishAlert)
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { useDeletePaymentMade } from 'hooks/query';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment made delete alert.
|
||||||
|
*/
|
||||||
|
function PaymentMadeDeleteAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { paymentMadeId },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const {
|
||||||
|
mutateAsync: deletePaymentMadeMutate,
|
||||||
|
isLoading,
|
||||||
|
} = useDeletePaymentMade();
|
||||||
|
|
||||||
|
// Handle cancel payment made.
|
||||||
|
const handleCancelPaymentMadeDelete = () => {};
|
||||||
|
|
||||||
|
// Handle confirm delete payment made
|
||||||
|
const handleConfirmPaymentMadeDelete = () => {
|
||||||
|
deletePaymentMadeMutate(paymentMadeId)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_payment_made_has_been_deleted_successfully',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'delete'} />}
|
||||||
|
icon={'trash'}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelPaymentMadeDelete}
|
||||||
|
onConfirm={handleConfirmPaymentMadeDelete}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_this_payment_made_you_will_able_to_restore_it'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(PaymentMadeDeleteAlert);
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
FormattedHTMLMessage,
|
FormattedHTMLMessage,
|
||||||
useIntl,
|
useIntl,
|
||||||
} from 'react-intl';
|
} from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
|
||||||
|
import { useDeletePaymentReceive } from 'hooks/query';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
import withPaymentReceivesActions from 'containers/Sales/PaymentReceive/withPaymentReceivesActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -24,14 +24,14 @@ function PaymentReceiveDeleteAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { paymentReceiveId },
|
payload: { paymentReceiveId },
|
||||||
|
|
||||||
// #withPaymentReceivesActions
|
|
||||||
requestDeletePaymentReceive,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
mutateAsync: deletePaymentReceiveMutate,
|
||||||
|
isLoading,
|
||||||
|
} = useDeletePaymentReceive();
|
||||||
|
|
||||||
// Handle cancel payment Receive.
|
// Handle cancel payment Receive.
|
||||||
const handleCancelDeleteAlert = () => {
|
const handleCancelDeleteAlert = () => {
|
||||||
@@ -39,9 +39,8 @@ function PaymentReceiveDeleteAlert({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle confirm delete payment receive.
|
// Handle confirm delete payment receive.
|
||||||
const handleConfirmPaymentReceiveDelete = useCallback(() => {
|
const handleConfirmPaymentReceiveDelete = () => {
|
||||||
setLoading(true);
|
deletePaymentReceiveMutate(paymentReceiveId)
|
||||||
requestDeletePaymentReceive(paymentReceiveId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -49,14 +48,12 @@ function PaymentReceiveDeleteAlert({
|
|||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.invalidateQueries('paymentReceives-table');
|
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
}, [paymentReceiveId, requestDeletePaymentReceive, formatMessage]);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -81,5 +78,4 @@ function PaymentReceiveDeleteAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withPaymentReceivesActions,
|
|
||||||
)(PaymentReceiveDeleteAlert);
|
)(PaymentReceiveDeleteAlert);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
|
||||||
|
import { useCloseReceipt } from 'hooks/query';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
import withReceiptActions from 'containers/Sales/Receipt/withReceiptActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -20,24 +20,20 @@ function ReceiptCloseAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { receiptId },
|
payload: { receiptId },
|
||||||
|
|
||||||
// #withReceiptActions
|
|
||||||
requestCloseReceipt,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const { mutateAsync: closeReceiptMutate, isLoading } = useCloseReceipt();
|
||||||
|
|
||||||
// handle cancel delete alert.
|
// handle cancel delete alert.
|
||||||
const handleCancelDeleteAlert = () => {
|
const handleCancelDeleteAlert = () => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle confirm receipt close.
|
// Handle confirm receipt close.
|
||||||
const handleConfirmReceiptClose = useCallback(() => {
|
const handleConfirmReceiptClose = () => {
|
||||||
setLoading(true);
|
closeReceiptMutate(receiptId)
|
||||||
requestCloseReceipt(receiptId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -45,14 +41,12 @@ function ReceiptCloseAlert({
|
|||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.invalidateQueries('receipts-table');
|
|
||||||
})
|
})
|
||||||
.catch((error) => {})
|
.catch((error) => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
}, [receiptId, requestCloseReceipt, formatMessage]);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -74,5 +68,4 @@ function ReceiptCloseAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withReceiptActions,
|
|
||||||
)(ReceiptCloseAlert);
|
)(ReceiptCloseAlert);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
FormattedHTMLMessage,
|
FormattedHTMLMessage,
|
||||||
@@ -6,11 +6,12 @@ import {
|
|||||||
} from 'react-intl';
|
} from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
import { queryCache } from 'react-query';
|
||||||
|
|
||||||
|
import { useDeleteReceipt } from 'hooks/query';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
import withReceiptActions from 'containers/Sales/Receipt/withReceiptActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -24,24 +25,23 @@ function NameDeleteAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { receiptId },
|
payload: { receiptId },
|
||||||
|
|
||||||
// #withReceiptActions
|
|
||||||
requestDeleteReceipt,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
mutateAsync: deleteReceiptMutate,
|
||||||
|
isLoading
|
||||||
|
} = useDeleteReceipt();
|
||||||
|
|
||||||
// handle cancel delete alert.
|
// Handle cancel delete alert.
|
||||||
const handleCancelDeleteAlert = () => {
|
const handleCancelDeleteAlert = () => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
// handle confirm delete receipt
|
// Handle confirm delete receipt
|
||||||
const handleConfirmReceiptDelete = useCallback(() => {
|
const handleConfirmReceiptDelete = () => {
|
||||||
setLoading(true);
|
deleteReceiptMutate(receiptId)
|
||||||
requestDeleteReceipt(receiptId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -49,14 +49,12 @@ function NameDeleteAlert({
|
|||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.invalidateQueries('receipts-table');
|
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
});
|
});
|
||||||
}, [receiptId, requestDeleteReceipt, formatMessage]);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -81,5 +79,4 @@ function NameDeleteAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withReceiptActions,
|
|
||||||
)(NameDeleteAlert);
|
)(NameDeleteAlert);
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import { Intent, Alert } from '@blueprintjs/core';
|
|||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
import { transformErrors } from 'containers/Customers/utils';
|
import { transformErrors } from 'containers/Customers/utils';
|
||||||
|
|
||||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
import withVendorActions from 'containers/Vendors/withVendorActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
import {
|
||||||
|
useDeleteVendor
|
||||||
|
} from 'hooks/query';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vendor delete alert.
|
* Vendor delete alert.
|
||||||
@@ -24,24 +24,23 @@ function VendorDeleteAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { vendorId },
|
payload: { vendorId },
|
||||||
|
|
||||||
// #withVendorActions
|
|
||||||
requestDeleteVender,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [isLoading, setLoading] = useState(false);
|
const {
|
||||||
|
mutateAsync: deleteVendorMutate,
|
||||||
|
isLoading
|
||||||
|
} = useDeleteVendor();
|
||||||
|
|
||||||
// Handle cancel delete the vendor.
|
// Handle cancel delete the vendor.
|
||||||
const handleCancelDeleteAlert = () => {
|
const handleCancelDeleteAlert = () => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
// handle confirm delete vendor.
|
// Handle confirm delete vendor.
|
||||||
const handleConfirmDeleteVendor = useCallback(() => {
|
const handleConfirmDeleteVendor = useCallback(() => {
|
||||||
setLoading(true);
|
deleteVendorMutate(vendorId)
|
||||||
requestDeleteVender(vendorId)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
@@ -55,9 +54,8 @@ function VendorDeleteAlert({
|
|||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
}, [requestDeleteVender, vendorId, formatMessage]);
|
}, [deleteVendorMutate, name, closeAlert, vendorId, formatMessage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -80,7 +78,5 @@ function VendorDeleteAlert({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withVendorActions,
|
|
||||||
)(VendorDeleteAlert);
|
)(VendorDeleteAlert);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
NavbarGroup,
|
NavbarGroup,
|
||||||
NavbarDivider,
|
NavbarDivider,
|
||||||
@@ -11,36 +11,35 @@ import {
|
|||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import { If, Icon, DashboardActionViewsList } from 'components';
|
import { If, Icon, DashboardActionViewsList } from 'components';
|
||||||
|
|
||||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
import { useCustomersListContext } from './CustomersListProvider';
|
||||||
|
|
||||||
import withCustomers from 'containers/Customers/withCustomers';
|
import withCustomers from 'containers/Customers/withCustomers';
|
||||||
import withCustomersActions from 'containers/Customers/withCustomersActions';
|
import withCustomersActions from 'containers/Customers/withCustomersActions';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
const CustomerActionsBar = ({
|
/**
|
||||||
|
* Customers actions bar.
|
||||||
|
*/
|
||||||
|
function CustomerActionsBar({
|
||||||
// #withCustomers
|
// #withCustomers
|
||||||
customersViews,
|
|
||||||
customersSelectedRows,
|
customersSelectedRows,
|
||||||
|
|
||||||
//#withCustomersActions
|
//#withCustomersActions
|
||||||
addCustomersTableQueries,
|
addCustomersTableQueries,
|
||||||
changeCustomerView,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
openAlert,
|
openAlert,
|
||||||
|
}) {
|
||||||
// #ownProps
|
|
||||||
onFilterChanged,
|
|
||||||
}) => {
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
const { customersViews } = useCustomersListContext();
|
||||||
|
|
||||||
const onClickNewCustomer = useCallback(() => {
|
const onClickNewCustomer = useCallback(() => {
|
||||||
history.push('/customers/new');
|
history.push('/customers/new');
|
||||||
@@ -52,7 +51,6 @@ const CustomerActionsBar = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleTabChange = (viewId) => {
|
const handleTabChange = (viewId) => {
|
||||||
changeCustomerView(viewId.id || -1);
|
|
||||||
addCustomersTableQueries({
|
addCustomersTableQueries({
|
||||||
custom_view_id: viewId.id || null,
|
custom_view_id: viewId.id || null,
|
||||||
});
|
});
|
||||||
@@ -111,19 +109,9 @@ const CustomerActionsBar = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
|
||||||
resourceName: 'customers',
|
|
||||||
});
|
|
||||||
const withCustomersActionsBar = connect(mapStateToProps);
|
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withCustomersActionsBar,
|
|
||||||
withCustomersActions,
|
withCustomersActions,
|
||||||
withResourceDetail(({ resourceFields }) => ({
|
withCustomers(({ customersSelectedRows }) => ({
|
||||||
resourceFields,
|
|
||||||
})),
|
|
||||||
withCustomers(({ customersViews, customersSelectedRows }) => ({
|
|
||||||
customersViews,
|
|
||||||
customersSelectedRows,
|
customersSelectedRows,
|
||||||
})),
|
})),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FormGroup, Position, Classes, ControlGroup } from '@blueprintjs/core';
|
import { FormGroup, Position, Classes, ControlGroup } from '@blueprintjs/core';
|
||||||
import { DateInput } from '@blueprintjs/datetime';
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
@@ -13,21 +13,23 @@ import {
|
|||||||
} from 'components';
|
} from 'components';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
|
||||||
import withCurrencies from 'containers/Currencies/withCurrencies';
|
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
compose,
|
|
||||||
momentFormatter,
|
momentFormatter,
|
||||||
tansformDateValue,
|
tansformDateValue,
|
||||||
inputIntent,
|
inputIntent,
|
||||||
} from 'utils';
|
} from 'utils';
|
||||||
|
|
||||||
function CustomerFinancialPanel({
|
/**
|
||||||
// #withCurrencies
|
* Customer financial panel.
|
||||||
currenciesList,
|
*/
|
||||||
|
export default function CustomerFinancialPanel() {
|
||||||
|
const {
|
||||||
|
currencies,
|
||||||
|
customerId
|
||||||
|
} = useCustomerFormContext();
|
||||||
|
|
||||||
customerId,
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<div className={'tab-panel--financial'}>
|
<div className={'tab-panel--financial'}>
|
||||||
<Row>
|
<Row>
|
||||||
@@ -103,7 +105,7 @@ function CustomerFinancialPanel({
|
|||||||
inline={true}
|
inline={true}
|
||||||
>
|
>
|
||||||
<CurrencySelectList
|
<CurrencySelectList
|
||||||
currenciesList={currenciesList}
|
currenciesList={currencies}
|
||||||
selectedCurrencyCode={value}
|
selectedCurrencyCode={value}
|
||||||
onCurrencySelected={(currency) => {
|
onCurrencySelected={(currency) => {
|
||||||
form.setFieldValue('currency_code', currency.currency_code);
|
form.setFieldValue('currency_code', currency.currency_code);
|
||||||
@@ -118,7 +120,3 @@ function CustomerFinancialPanel({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withCurrencies(({ currenciesList }) => ({ currenciesList })),
|
|
||||||
)(CustomerFinancialPanel);
|
|
||||||
|
|||||||
@@ -13,40 +13,38 @@ import { FormattedMessage as T } from 'react-intl';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import { saveInvoke } from 'utils';
|
|
||||||
import { Icon } from 'components';
|
import { Icon } from 'components';
|
||||||
|
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customer floating actions bar.
|
* Customer floating actions bar.
|
||||||
*/
|
*/
|
||||||
export default function CustomerFloatingActions({
|
export default function CustomerFloatingActions() {
|
||||||
onSubmitClick,
|
// Customer form context.
|
||||||
onCancelClick,
|
const { customerId, setSubmitPayload } = useCustomerFormContext();
|
||||||
isSubmitting,
|
|
||||||
customerId,
|
|
||||||
}) {
|
|
||||||
const { resetForm, submitForm } = useFormikContext();
|
|
||||||
|
|
||||||
|
// Formik context.
|
||||||
|
const { resetForm, submitForm, isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
// Handle submit button click.
|
||||||
const handleSubmitBtnClick = (event) => {
|
const handleSubmitBtnClick = (event) => {
|
||||||
saveInvoke(onSubmitClick, event, {
|
setSubmitPayload({ noRedirect: false });
|
||||||
noRedirect: false,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle cancel button click.
|
||||||
const handleCancelBtnClick = (event) => {
|
const handleCancelBtnClick = (event) => {
|
||||||
saveInvoke(onCancelClick, event);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// handle clear button clicl.
|
||||||
const handleClearBtnClick = (event) => {
|
const handleClearBtnClick = (event) => {
|
||||||
// saveInvoke(onClearClick, event);
|
|
||||||
resetForm();
|
resetForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle submit & new button click.
|
||||||
const handleSubmitAndNewClick = (event) => {
|
const handleSubmitAndNewClick = (event) => {
|
||||||
submitForm();
|
submitForm();
|
||||||
saveInvoke(onSubmitClick, event, {
|
setSubmitPayload({ noRedirect: true });
|
||||||
noRedirect: true,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -55,6 +53,7 @@ export default function CustomerFloatingActions({
|
|||||||
{/* ----------- Save and New ----------- */}
|
{/* ----------- Save and New ----------- */}
|
||||||
<Button
|
<Button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
|
loading={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={handleSubmitBtnClick}
|
onClick={handleSubmitBtnClick}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useState, useMemo, useCallback, useEffect } from 'react';
|
import React, { useMemo, useEffect } from 'react';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { Formik, Form } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -16,14 +16,10 @@ import CustomersTabs from 'containers/Customers/CustomersTabs';
|
|||||||
import CustomerFloatingActions from './CustomerFloatingActions';
|
import CustomerFloatingActions from './CustomerFloatingActions';
|
||||||
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withCustomerDetail from 'containers/Customers/withCustomerDetail';
|
|
||||||
import withCustomersActions from 'containers/Customers/withCustomersActions';
|
|
||||||
import withMediaActions from 'containers/Media/withMediaActions';
|
|
||||||
import withCustomers from 'containers/Customers//withCustomers';
|
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
import useMedia from 'hooks/useMedia';
|
|
||||||
|
|
||||||
import { compose, transformToForm } from 'utils';
|
import { compose, transformToForm } from 'utils';
|
||||||
|
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||||
|
|
||||||
const defaultInitialValues = {
|
const defaultInitialValues = {
|
||||||
customer_type: 'business',
|
customer_type: 'business',
|
||||||
@@ -68,43 +64,20 @@ function CustomerForm({
|
|||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
|
||||||
// #withCustomers
|
|
||||||
customers,
|
|
||||||
|
|
||||||
// #withCustomerDetail
|
|
||||||
customer,
|
|
||||||
|
|
||||||
// #withSettings
|
// #withSettings
|
||||||
baseCurrency,
|
baseCurrency,
|
||||||
|
|
||||||
// #withCustomersActions
|
|
||||||
requestSubmitCustomer,
|
|
||||||
requestEditCustomer,
|
|
||||||
|
|
||||||
// #withMediaActions
|
|
||||||
requestSubmitMedia,
|
|
||||||
requestDeleteMedia,
|
|
||||||
|
|
||||||
// #Props
|
|
||||||
customerId,
|
|
||||||
onFormSubmit,
|
|
||||||
onCancelForm,
|
|
||||||
}) {
|
}) {
|
||||||
const isNewMode = !customerId;
|
const {
|
||||||
const [submitPayload, setSubmitPayload] = useState({});
|
customer,
|
||||||
|
customerId,
|
||||||
|
submitPayload,
|
||||||
|
editCustomerMutate,
|
||||||
|
createCustomerMutate,
|
||||||
|
} = useCustomerFormContext();
|
||||||
|
|
||||||
|
const isNewMode = !customerId;
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const {
|
|
||||||
setFiles,
|
|
||||||
saveMedia,
|
|
||||||
deletedFiles,
|
|
||||||
setDeletedFiles,
|
|
||||||
deleteMedia,
|
|
||||||
} = useMedia({
|
|
||||||
saveCallback: requestSubmitMedia,
|
|
||||||
deleteCallback: requestDeleteMedia,
|
|
||||||
});
|
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
customer_type: Yup.string()
|
customer_type: Yup.string()
|
||||||
@@ -158,7 +131,7 @@ function CustomerForm({
|
|||||||
currency_code: baseCurrency,
|
currency_code: baseCurrency,
|
||||||
...transformToForm(customer, defaultInitialValues),
|
...transformToForm(customer, defaultInitialValues),
|
||||||
}),
|
}),
|
||||||
[customer, defaultInitialValues],
|
[customer, baseCurrency],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -196,53 +169,14 @@ function CustomerForm({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (customer && customer.id) {
|
if (customer && customer.id) {
|
||||||
requestEditCustomer(customer.id, formValues)
|
editCustomerMutate(customer.id, formValues)
|
||||||
.then(onSuccess)
|
.then(onSuccess)
|
||||||
.catch(onError);
|
.catch(onError);
|
||||||
} else {
|
} else {
|
||||||
requestSubmitCustomer(formValues).then(onSuccess).catch(onError);
|
createCustomerMutate(formValues).then(onSuccess).catch(onError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialAttachmentFiles = useMemo(() => {
|
|
||||||
return customer && customer.media
|
|
||||||
? customer.media.map((attach) => ({
|
|
||||||
preview: attach.attachment_file,
|
|
||||||
upload: true,
|
|
||||||
metadata: { ...attach },
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDropFiles = useCallback((_files) => {
|
|
||||||
setFiles(_files.filter((file) => file.uploaded === false));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDeleteFile = useCallback(
|
|
||||||
(_deletedFiles) => {
|
|
||||||
_deletedFiles.forEach((deletedFile) => {
|
|
||||||
if (deletedFile.uploaded && deletedFile.metadata.id) {
|
|
||||||
setDeletedFiles([...deletedFiles, deletedFile.metadata.id]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setDeletedFiles, deletedFiles],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCancelClick = useCallback(
|
|
||||||
(event) => {
|
|
||||||
history.goBack();
|
|
||||||
},
|
|
||||||
[history],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmitClick = useCallback(
|
|
||||||
(event, payload) => {
|
|
||||||
setSubmitPayload({ ...payload });
|
|
||||||
},
|
|
||||||
[setSubmitPayload],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_CUSTOMER)}>
|
<div className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_CUSTOMER)}>
|
||||||
<Formik
|
<Formik
|
||||||
@@ -250,42 +184,29 @@ function CustomerForm({
|
|||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
onSubmit={handleFormSubmit}
|
onSubmit={handleFormSubmit}
|
||||||
>
|
>
|
||||||
{({ isSubmitting }) => (
|
<Form>
|
||||||
<Form>
|
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
|
<CustomerFormPrimarySection />
|
||||||
<CustomerFormPrimarySection />
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={'page-form__after-priamry-section'}>
|
<div className={'page-form__after-priamry-section'}>
|
||||||
<CustomerFormAfterPrimarySection />
|
<CustomerFormAfterPrimarySection />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
||||||
<CustomersTabs customer={customerId} />
|
<CustomersTabs />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CustomerFloatingActions
|
<CustomerFloatingActions />
|
||||||
isSubmitting={isSubmitting}
|
</Form>
|
||||||
customerId={customer}
|
|
||||||
onSubmitClick={handleSubmitClick}
|
|
||||||
onCancelClick={handleCancelClick}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</Formik>
|
</Formik>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withCustomerDetail,
|
|
||||||
withCustomers(({ customers }) => ({
|
|
||||||
customers,
|
|
||||||
})),
|
|
||||||
withSettings(({ organizationSettings }) => ({
|
withSettings(({ organizationSettings }) => ({
|
||||||
baseCurrency: organizationSettings?.baseCurrency,
|
baseCurrency: organizationSettings?.baseCurrency,
|
||||||
})),
|
})),
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withCustomersActions,
|
|
||||||
withMediaActions,
|
|
||||||
)(CustomerForm);
|
)(CustomerForm);
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React from 'react';
|
||||||
import { useParams, useHistory } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
|
|
||||||
import { DashboardCard } from 'components';
|
import { DashboardCard } from 'components';
|
||||||
import CustomerForm from 'containers/Customers/CustomerForm';
|
import CustomerForm from './CustomerForm';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import { CustomerFormProvider } from './CustomerFormProvider';
|
||||||
|
|
||||||
import withCustomersActions from './withCustomersActions';
|
import withCustomersActions from './withCustomersActions';
|
||||||
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
|
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
|
||||||
@@ -13,60 +12,19 @@ import { compose } from 'utils';
|
|||||||
|
|
||||||
import 'style/pages/Customers/PageForm.scss';
|
import 'style/pages/Customers/PageForm.scss';
|
||||||
|
|
||||||
function CustomerFormPage({
|
function CustomerFormPage() {
|
||||||
// // #withDashboardActions
|
|
||||||
// changePageTitle,
|
|
||||||
|
|
||||||
// formik,
|
|
||||||
//#withCustomersActions
|
|
||||||
requestFetchCustomers,
|
|
||||||
requestFetchCustomer,
|
|
||||||
|
|
||||||
// #wihtCurrenciesActions
|
|
||||||
requestFetchCurrencies,
|
|
||||||
}) {
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
// Handle fetch customers data table
|
|
||||||
const fetchCustomers = useQuery('customers-table', () =>
|
|
||||||
requestFetchCustomers({}),
|
|
||||||
);
|
|
||||||
// Handle fetch customer details.
|
|
||||||
const fetchCustomer = useQuery(
|
|
||||||
['customer', id],
|
|
||||||
(key, customerId) => requestFetchCustomer(customerId),
|
|
||||||
{ enabled: id && id },
|
|
||||||
);
|
|
||||||
// Handle fetch Currencies data table
|
|
||||||
const fetchCurrencies = useQuery('currencies', () =>
|
|
||||||
requestFetchCurrencies(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleFormSubmit = useCallback((payload) => {}, [history]);
|
|
||||||
|
|
||||||
const handleCancel = useCallback(() => {
|
|
||||||
history.goBack();
|
|
||||||
}, [history]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<CustomerFormProvider customerId={id}>
|
||||||
loading={
|
|
||||||
fetchCustomer.isFetching ||
|
|
||||||
fetchCustomers.isFetching ||
|
|
||||||
fetchCurrencies.isFetching
|
|
||||||
}
|
|
||||||
name={'customer-form'}
|
|
||||||
>
|
|
||||||
<DashboardCard page>
|
<DashboardCard page>
|
||||||
<CustomerForm
|
<CustomerForm />
|
||||||
onFormSubmit={handleFormSubmit}
|
|
||||||
customerId={id}
|
|
||||||
onCancelForm={handleCancel}
|
|
||||||
/>
|
|
||||||
</DashboardCard>
|
</DashboardCard>
|
||||||
</DashboardInsider>
|
</CustomerFormProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withCustomersActions, withCurrenciesActions)(CustomerFormPage);
|
export default compose(
|
||||||
|
withCustomersActions,
|
||||||
|
withCurrenciesActions,
|
||||||
|
)(CustomerFormPage);
|
||||||
|
|||||||
65
client/src/containers/Customers/CustomerFormProvider.js
Normal file
65
client/src/containers/Customers/CustomerFormProvider.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import React, { useState, createContext } from 'react';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
import {
|
||||||
|
useCustomers,
|
||||||
|
useCustomer,
|
||||||
|
useCurrencies,
|
||||||
|
useCreateCustomer,
|
||||||
|
useEditCustomer,
|
||||||
|
} from 'hooks/query';
|
||||||
|
|
||||||
|
const CustomerFormContext = createContext();
|
||||||
|
|
||||||
|
function CustomerFormProvider({ customerId, ...props }) {
|
||||||
|
// Handle fetch customer details.
|
||||||
|
const { data: customer, isFetching: isCustomerLoading } = useCustomer(
|
||||||
|
customerId,
|
||||||
|
{
|
||||||
|
enabled: !!customerId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle fetch customers data table
|
||||||
|
const {
|
||||||
|
data: { customers },
|
||||||
|
isFetching: isCustomersLoading,
|
||||||
|
} = useCustomers();
|
||||||
|
|
||||||
|
// Handle fetch Currencies data table
|
||||||
|
const { data: currencies, isFetching: isCurrenciesLoading } = useCurrencies();
|
||||||
|
|
||||||
|
// Form submit payload.
|
||||||
|
const [submitPayload, setSubmitPayload] = useState({});
|
||||||
|
|
||||||
|
const { mutateAsync: editCustomerMutate } = useEditCustomer();
|
||||||
|
const { mutateAsync: createCustomerMutate } = useCreateCustomer();
|
||||||
|
|
||||||
|
const provider = {
|
||||||
|
customerId,
|
||||||
|
customer,
|
||||||
|
customers,
|
||||||
|
currencies,
|
||||||
|
submitPayload,
|
||||||
|
|
||||||
|
isCustomerLoading,
|
||||||
|
isCustomersLoading,
|
||||||
|
isCurrenciesLoading,
|
||||||
|
|
||||||
|
setSubmitPayload,
|
||||||
|
editCustomerMutate,
|
||||||
|
createCustomerMutate
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider
|
||||||
|
loading={isCustomerLoading || isCustomerLoading || isCurrenciesLoading}
|
||||||
|
name={'customer-form'}
|
||||||
|
>
|
||||||
|
<CustomerFormContext.Provider value={provider} {...props} />
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useCustomerFormContext = () => React.useContext(CustomerFormContext);
|
||||||
|
|
||||||
|
export { CustomerFormProvider, useCustomerFormContext };
|
||||||
@@ -8,12 +8,11 @@ import {
|
|||||||
Position,
|
Position,
|
||||||
Intent,
|
Intent,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useIsValuePassed } from 'hooks';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import CustomersEmptyStatus from './CustomersEmptyStatus';
|
import CustomersEmptyStatus from './CustomersEmptyStatus';
|
||||||
import { DataTable, Icon, Money, Choose, LoadingIndicator } from 'components';
|
import { DataTable, Icon, Money, Choose } from 'components';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
import withCustomers from './withCustomers';
|
import withCustomers from './withCustomers';
|
||||||
@@ -25,7 +24,19 @@ const AvatarCell = (row) => {
|
|||||||
return <span className="avatar">{firstLettersArgs(row.display_name)}</span>;
|
return <span className="avatar">{firstLettersArgs(row.display_name)}</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CustomerTable = ({
|
const PhoneNumberAccessor = (row) => (
|
||||||
|
<div>
|
||||||
|
<div className={'work_phone'}>{row.work_phone}</div>
|
||||||
|
<div className={'personal_phone'}>{row.personal_phone}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const BalanceAccessor = (row) => {
|
||||||
|
return (<Money amount={row.closing_balance} currency={row.currency_code} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function CustomerTable({
|
||||||
//#withCustomers
|
//#withCustomers
|
||||||
customers,
|
customers,
|
||||||
customersLoading,
|
customersLoading,
|
||||||
@@ -42,9 +53,8 @@ const CustomerTable = ({
|
|||||||
onDeleteCustomer,
|
onDeleteCustomer,
|
||||||
onFetchData,
|
onFetchData,
|
||||||
onSelectedRowsChange,
|
onSelectedRowsChange,
|
||||||
}) => {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const isLoadedBefore = useIsValuePassed(loading, false);
|
|
||||||
|
|
||||||
// Customers actions list.
|
// Customers actions list.
|
||||||
const renderContextMenu = useMemo(
|
const renderContextMenu = useMemo(
|
||||||
@@ -125,21 +135,14 @@ const CustomerTable = ({
|
|||||||
{
|
{
|
||||||
id: 'phone_number',
|
id: 'phone_number',
|
||||||
Header: formatMessage({ id: 'phone_number' }),
|
Header: formatMessage({ id: 'phone_number' }),
|
||||||
accessor: (row) => (
|
accessor: PhoneNumberAccessor,
|
||||||
<div>
|
|
||||||
<div className={'work_phone'}>{row.work_phone}</div>
|
|
||||||
<div className={'personal_phone'}>{row.personal_phone}</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
className: 'phone_number',
|
className: 'phone_number',
|
||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'receivable_balance',
|
id: 'receivable_balance',
|
||||||
Header: formatMessage({ id: 'receivable_balance' }),
|
Header: formatMessage({ id: 'receivable_balance' }),
|
||||||
accessor: (r) => (
|
accessor: BalanceAccessor,
|
||||||
<Money amount={r.closing_balance} currency={r.currency_code} />
|
|
||||||
),
|
|
||||||
className: 'receivable_balance',
|
className: 'receivable_balance',
|
||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
@@ -193,35 +196,33 @@ const CustomerTable = ({
|
|||||||
].every((condition) => condition === true);
|
].every((condition) => condition === true);
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
|
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
|
||||||
<LoadingIndicator loading={customersLoading && !isLoadedBefore}>
|
<Choose>
|
||||||
<Choose>
|
<Choose.When condition={showEmptyStatus}>
|
||||||
<Choose.When condition={showEmptyStatus}>
|
<CustomersEmptyStatus />
|
||||||
<CustomersEmptyStatus />
|
</Choose.When>
|
||||||
</Choose.When>
|
|
||||||
|
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<DataTable
|
<DataTable
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={customers}
|
data={customers}
|
||||||
onFetchData={handleFetchData}
|
onFetchData={handleFetchData}
|
||||||
selectionColumn={true}
|
selectionColumn={true}
|
||||||
expandable={false}
|
expandable={false}
|
||||||
sticky={true}
|
sticky={true}
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
spinnerProps={{ size: 30 }}
|
spinnerProps={{ size: 30 }}
|
||||||
rowContextMenu={rowContextMenu}
|
rowContextMenu={rowContextMenu}
|
||||||
pagination={true}
|
pagination={true}
|
||||||
manualSortBy={true}
|
manualSortBy={true}
|
||||||
pagesCount={customerPagination.pagesCount}
|
pagesCount={customerPagination.pagesCount}
|
||||||
autoResetSortBy={false}
|
autoResetSortBy={false}
|
||||||
autoResetPage={false}
|
autoResetPage={false}
|
||||||
initialPageSize={customersTableQuery.page_size}
|
initialPageSize={customersTableQuery.page_size}
|
||||||
initialPageIndex={customersTableQuery.page - 1}
|
initialPageIndex={customersTableQuery.page - 1}
|
||||||
/>
|
/>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
</LoadingIndicator>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useIntl } from 'react-intl';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
|
||||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||||
|
|
||||||
import CustomerActionsBar from 'containers/Customers/CustomerActionsBar';
|
import CustomerActionsBar from 'containers/Customers/CustomerActionsBar';
|
||||||
import CustomersAlerts from 'containers/Customers/CustomersAlerts';
|
import CustomersAlerts from 'containers/Customers/CustomersAlerts';
|
||||||
import CustomersViewPage from 'containers/Customers/CustomersViewPage';
|
import CustomersViewPage from 'containers/Customers/CustomersViewPage';
|
||||||
|
import { CustomersListProvider } from './CustomersListProvider';
|
||||||
|
|
||||||
import withCustomers from 'containers/Customers/withCustomers';
|
import withCustomers from 'containers/Customers/withCustomers';
|
||||||
import withCustomersActions from 'containers/Customers/withCustomersActions';
|
|
||||||
import withResourceActions from 'containers/Resources/withResourcesActions';
|
|
||||||
import withViewsActions from 'containers/Views/withViewsActions';
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
@@ -25,59 +22,29 @@ function CustomersList({
|
|||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
|
||||||
// #withResourceActions
|
|
||||||
requestFetchResourceViews,
|
|
||||||
|
|
||||||
// #withCustomers
|
// #withCustomers
|
||||||
customersTableQuery,
|
customersTableQuery,
|
||||||
|
|
||||||
// #withCustomersActions
|
|
||||||
requestFetchCustomers,
|
|
||||||
addCustomersTableQueries,
|
|
||||||
}) {
|
}) {
|
||||||
const [tableLoading, setTableLoading] = useState(false);
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle(formatMessage({ id: 'customers_list' }));
|
changePageTitle(formatMessage({ id: 'customers_list' }));
|
||||||
}, [changePageTitle, formatMessage]);
|
}, [changePageTitle, formatMessage]);
|
||||||
|
|
||||||
// Fetch customers resource views and fields.
|
|
||||||
const fetchResourceViews = useQuery(
|
|
||||||
['resource-views', 'customers'],
|
|
||||||
(key, resourceName) => requestFetchResourceViews(resourceName),
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetchCustomers = useQuery(
|
|
||||||
['customers-table', customersTableQuery],
|
|
||||||
(key, query) => requestFetchCustomers({ ...query }),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (tableLoading && !fetchCustomers.isFetching) {
|
|
||||||
setTableLoading(false);
|
|
||||||
}
|
|
||||||
}, [tableLoading, fetchCustomers.isFetching]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<CustomersListProvider query={customersTableQuery}>
|
||||||
loading={fetchResourceViews.isFetching}
|
|
||||||
name={'customers-list'}
|
|
||||||
>
|
|
||||||
<CustomerActionsBar />
|
<CustomerActionsBar />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
<CustomersViewPage />
|
<CustomersViewPage />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
<CustomersAlerts />
|
<CustomersAlerts />
|
||||||
</DashboardInsider>
|
</CustomersListProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withResourceActions,
|
|
||||||
withCustomersActions,
|
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withViewsActions,
|
|
||||||
withCustomers(({ customersTableQuery }) => ({ customersTableQuery })),
|
withCustomers(({ customersTableQuery }) => ({ customersTableQuery })),
|
||||||
)(CustomersList);
|
)(CustomersList);
|
||||||
|
|||||||
41
client/src/containers/Customers/CustomersListProvider.js
Normal file
41
client/src/containers/Customers/CustomersListProvider.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import React, { createContext } from 'react';
|
||||||
|
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
import { useResourceViews, useCustomers } from 'hooks/query';
|
||||||
|
|
||||||
|
const CustomersListContext = createContext();
|
||||||
|
|
||||||
|
function CustomersListProvider({ query, ...props }) {
|
||||||
|
// Fetch customers resource views and fields.
|
||||||
|
const {
|
||||||
|
data: customersViews,
|
||||||
|
isFetching: isCustomersViewsLoading,
|
||||||
|
} = useResourceViews('customers');
|
||||||
|
|
||||||
|
// Fetches customers data with pagination meta.
|
||||||
|
const {
|
||||||
|
data: { customers, pagination },
|
||||||
|
isFetching: isCustomersLoading,
|
||||||
|
} = useCustomers(query);
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
customersViews,
|
||||||
|
customers,
|
||||||
|
pagination,
|
||||||
|
|
||||||
|
isCustomersViewsLoading,
|
||||||
|
isCustomersLoading,
|
||||||
|
|
||||||
|
isEmptyStatus: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider loading={isCustomersViewsLoading} name={'customers-list'}>
|
||||||
|
<CustomersListContext.Provider value={state} {...props} />
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useCustomersListContext = () => React.useContext(CustomersListContext);
|
||||||
|
|
||||||
|
export { CustomersListProvider, useCustomersListContext };
|
||||||
@@ -6,7 +6,7 @@ import CustomerAttachmentTabs from './CustomerAttachmentTabs';
|
|||||||
import CustomerFinancialPanel from './CustomerFinancialPanel';
|
import CustomerFinancialPanel from './CustomerFinancialPanel';
|
||||||
import CustomerNotePanel from './CustomerNotePanel';
|
import CustomerNotePanel from './CustomerNotePanel';
|
||||||
|
|
||||||
export default function CustomersTabs({ customer }) {
|
export default function CustomersTabs() {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -20,7 +20,7 @@ export default function CustomersTabs({ customer }) {
|
|||||||
<Tab
|
<Tab
|
||||||
id={'financial'}
|
id={'financial'}
|
||||||
title={formatMessage({ id: 'financial_details' })}
|
title={formatMessage({ id: 'financial_details' })}
|
||||||
panel={<CustomerFinancialPanel customerId={customer} />}
|
panel={<CustomerFinancialPanel />}
|
||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
id={'address'}
|
id={'address'}
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ function CustomersViewPage({
|
|||||||
>
|
>
|
||||||
<CustomersViewsTabs />
|
<CustomersViewsTabs />
|
||||||
<CustomersTable
|
<CustomersTable
|
||||||
onDeleteCustomer={handleDeleteCustomer}
|
// onDeleteCustomer={handleDeleteCustomer}
|
||||||
onEditCustomer={handleEditCustomer}
|
// onEditCustomer={handleEditCustomer}
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
// onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|||||||
@@ -1,43 +1,25 @@
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
|
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
|
||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
import { useParams, withRouter } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { DashboardViewsTabs } from 'components';
|
import { DashboardViewsTabs } from 'components';
|
||||||
|
|
||||||
import withCustomers from 'containers/Customers/withCustomers';
|
|
||||||
import withCustomersActions from 'containers/Customers/withCustomersActions';
|
import withCustomersActions from 'containers/Customers/withCustomersActions';
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withViewDetail from 'containers/Views/withViewDetails';
|
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
|
import { useCustomersListContext } from './CustomersListProvider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customers views tabs.
|
* Customers views tabs.
|
||||||
*/
|
*/
|
||||||
function CustomersViewsTabs({
|
function CustomersViewsTabs({
|
||||||
// #withViewDetail
|
|
||||||
viewId,
|
|
||||||
viewItem,
|
|
||||||
|
|
||||||
// #withCustomers
|
|
||||||
customersViews,
|
|
||||||
|
|
||||||
// #withCustomersActions
|
// #withCustomersActions
|
||||||
addCustomersTableQueries,
|
addCustomersTableQueries,
|
||||||
changeCustomerView,
|
|
||||||
|
|
||||||
// #withDashboardActions
|
|
||||||
setTopbarEditView,
|
|
||||||
changePageSubtitle,
|
|
||||||
}) {
|
}) {
|
||||||
const { custom_view_id: customViewId = null } = useParams();
|
const { custom_view_id: customViewId = null } = useParams();
|
||||||
|
const { customersViews } = useCustomersListContext();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
|
|
||||||
setTopbarEditView(customViewId);
|
|
||||||
}, [customViewId]);
|
|
||||||
|
|
||||||
const tabs = useMemo(() =>
|
const tabs = useMemo(() =>
|
||||||
customersViews.map(
|
customersViews.map(
|
||||||
(view) => ({
|
(view) => ({
|
||||||
@@ -45,14 +27,13 @@ function CustomersViewsTabs({
|
|||||||
}),
|
}),
|
||||||
[customersViews],
|
[customersViews],
|
||||||
),
|
),
|
||||||
|
[customersViews]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTabsChange = (viewId) => {
|
const handleTabsChange = (viewId) => {
|
||||||
changeCustomerView(viewId || -1);
|
|
||||||
addCustomersTableQueries({
|
addCustomersTableQueries({
|
||||||
custom_view_id: viewId || null,
|
custom_view_id: viewId || null,
|
||||||
});
|
});
|
||||||
setTopbarEditView(viewId);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -69,19 +50,7 @@ function CustomersViewsTabs({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => ({
|
|
||||||
viewId: ownProps.match.params.custom_view_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const withCustomersViewsTabs = connect(mapStateToProps);
|
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withRouter,
|
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withCustomersViewsTabs,
|
|
||||||
withCustomersActions,
|
withCustomersActions,
|
||||||
withViewDetail(),
|
|
||||||
withCustomers(({ customersViews }) => ({
|
|
||||||
customersViews,
|
|
||||||
})),
|
|
||||||
)(CustomersViewsTabs);
|
)(CustomersViewsTabs);
|
||||||
|
|||||||
@@ -3,24 +3,29 @@ import { Intent } from '@blueprintjs/core';
|
|||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import { useQuery, queryCache } from 'react-query';
|
|
||||||
import { AppToaster, DialogContent } from 'components';
|
import { AppToaster, DialogContent } from 'components';
|
||||||
|
|
||||||
import AccountFormDialogFields from './AccountFormDialogFields';
|
import AccountFormDialogFields from './AccountFormDialogFields';
|
||||||
|
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
|
||||||
import withAccountDetail from 'containers/Accounts/withAccountDetail';
|
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import {
|
import {
|
||||||
EditAccountFormSchema,
|
EditAccountFormSchema,
|
||||||
CreateAccountFormSchema,
|
CreateAccountFormSchema,
|
||||||
} from './AccountForm.schema';
|
} from './AccountForm.schema';
|
||||||
|
import {
|
||||||
|
useAccounts,
|
||||||
|
useAccountsTypes,
|
||||||
|
useCreateAccount,
|
||||||
|
useAccount,
|
||||||
|
useEditAccount
|
||||||
|
} from 'hooks/query';
|
||||||
import { compose, transformToForm } from 'utils';
|
import { compose, transformToForm } from 'utils';
|
||||||
import { transformApiErrors, transformAccountToForm } from './utils';
|
import { transformApiErrors, transformAccountToForm } from './utils';
|
||||||
|
|
||||||
import 'style/pages/Accounts/AccountFormDialog.scss';
|
import 'style/pages/Accounts/AccountFormDialog.scss';
|
||||||
|
|
||||||
|
|
||||||
|
// Default initial form values.
|
||||||
const defaultInitialValues = {
|
const defaultInitialValues = {
|
||||||
account_type: '',
|
account_type: '',
|
||||||
parent_account_id: '',
|
parent_account_id: '',
|
||||||
@@ -34,16 +39,7 @@ const defaultInitialValues = {
|
|||||||
* Account form dialog content.
|
* Account form dialog content.
|
||||||
*/
|
*/
|
||||||
function AccountFormDialogContent({
|
function AccountFormDialogContent({
|
||||||
// #withAccountDetail
|
// #withDialogActions
|
||||||
account,
|
|
||||||
|
|
||||||
// #withAccountsActions
|
|
||||||
requestFetchAccounts,
|
|
||||||
requestFetchAccountTypes,
|
|
||||||
requestFetchAccount,
|
|
||||||
requestSubmitAccount,
|
|
||||||
requestEditAccount,
|
|
||||||
|
|
||||||
closeDialog,
|
closeDialog,
|
||||||
|
|
||||||
// #ownProp
|
// #ownProp
|
||||||
@@ -61,6 +57,27 @@ function AccountFormDialogContent({
|
|||||||
? CreateAccountFormSchema
|
? CreateAccountFormSchema
|
||||||
: EditAccountFormSchema;
|
: EditAccountFormSchema;
|
||||||
|
|
||||||
|
const { mutateAsync: createAccountMutate } = useCreateAccount();
|
||||||
|
const { mutateAsync: editAccountMutate } = useEditAccount();
|
||||||
|
|
||||||
|
// Fetches accounts list.
|
||||||
|
const {
|
||||||
|
data: accounts,
|
||||||
|
isLoading: isAccountsLoading,
|
||||||
|
} = useAccounts();
|
||||||
|
|
||||||
|
// Fetches accounts types.
|
||||||
|
const {
|
||||||
|
data: accountsTypes,
|
||||||
|
isLoading: isAccountsTypesLoading
|
||||||
|
} = useAccountsTypes();
|
||||||
|
|
||||||
|
// Fetches the specific account details.
|
||||||
|
const {
|
||||||
|
data: account,
|
||||||
|
isLoading: isAccountLoading,
|
||||||
|
} = useAccount(accountId, { enabled: !!accountId });
|
||||||
|
|
||||||
// Callbacks handles form submit.
|
// Callbacks handles form submit.
|
||||||
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
const form = omit(values, ['subaccount']);
|
const form = omit(values, ['subaccount']);
|
||||||
@@ -71,11 +88,6 @@ function AccountFormDialogContent({
|
|||||||
// Handle request success.
|
// Handle request success.
|
||||||
const handleSuccess = () => {
|
const handleSuccess = () => {
|
||||||
closeDialog(dialogName);
|
closeDialog(dialogName);
|
||||||
queryCache.invalidateQueries('accounts-table');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
queryCache.invalidateQueries('accounts-list');
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage(
|
message: formatMessage(
|
||||||
@@ -94,16 +106,16 @@ function AccountFormDialogContent({
|
|||||||
};
|
};
|
||||||
// Handle request error.
|
// Handle request error.
|
||||||
const handleError = (errors) => {
|
const handleError = (errors) => {
|
||||||
const errorsTransformed = transformApiErrors(errors);
|
// const errorsTransformed = transformApiErrors(errors);
|
||||||
setErrors({ ...errorsTransformed });
|
// setErrors({ ...errorsTransformed });
|
||||||
setSubmitting(false);
|
// setSubmitting(false);
|
||||||
};
|
};
|
||||||
if (accountId) {
|
if (accountId) {
|
||||||
requestEditAccount(accountId, form)
|
editAccountMutate(accountId, form)
|
||||||
.then(handleSuccess)
|
.then(handleSuccess)
|
||||||
.catch(handleError);
|
.catch(handleError);
|
||||||
} else {
|
} else {
|
||||||
requestSubmitAccount({ form }).then(handleSuccess).catch(handleError);
|
createAccountMutate({ ...form }).then(handleSuccess).catch(handleError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -130,27 +142,10 @@ function AccountFormDialogContent({
|
|||||||
closeDialog(dialogName);
|
closeDialog(dialogName);
|
||||||
}, [closeDialog, dialogName]);
|
}, [closeDialog, dialogName]);
|
||||||
|
|
||||||
// Fetches accounts list.
|
|
||||||
const fetchAccountsList = useQuery('accounts-list', () =>
|
|
||||||
requestFetchAccounts(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fetches accounts types.
|
|
||||||
const fetchAccountsTypes = useQuery('accounts-types-list', () =>
|
|
||||||
requestFetchAccountTypes(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch the given account id on edit mode.
|
|
||||||
const fetchAccount = useQuery(
|
|
||||||
['account', accountId],
|
|
||||||
(key, _id) => requestFetchAccount(_id),
|
|
||||||
{ enabled: accountId },
|
|
||||||
);
|
|
||||||
|
|
||||||
const isFetching =
|
const isFetching =
|
||||||
fetchAccountsList.isFetching ||
|
isAccountsLoading ||
|
||||||
fetchAccountsTypes.isFetching ||
|
isAccountsTypesLoading ||
|
||||||
fetchAccount.isFetching;
|
isAccountLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent isLoading={isFetching}>
|
<DialogContent isLoading={isFetching}>
|
||||||
@@ -160,6 +155,8 @@ function AccountFormDialogContent({
|
|||||||
onSubmit={handleFormSubmit}
|
onSubmit={handleFormSubmit}
|
||||||
>
|
>
|
||||||
<AccountFormDialogFields
|
<AccountFormDialogFields
|
||||||
|
accounts={accounts}
|
||||||
|
accountsTypes={accountsTypes}
|
||||||
dialogName={dialogName}
|
dialogName={dialogName}
|
||||||
action={action}
|
action={action}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
@@ -170,7 +167,5 @@ function AccountFormDialogContent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withAccountsActions,
|
|
||||||
withAccountDetail,
|
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
)(AccountFormDialogContent);
|
)(AccountFormDialogContent);
|
||||||
|
|||||||
@@ -28,11 +28,9 @@ import { useAutofocus } from 'hooks';
|
|||||||
* Account form dialogs fields.
|
* Account form dialogs fields.
|
||||||
*/
|
*/
|
||||||
function AccountFormDialogFields({
|
function AccountFormDialogFields({
|
||||||
// #ownPropscl
|
// #ownProps
|
||||||
onClose,
|
onClose,
|
||||||
action,
|
action,
|
||||||
|
|
||||||
// #withAccounts
|
|
||||||
accounts,
|
accounts,
|
||||||
accountsTypes,
|
accountsTypes,
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -4,10 +4,16 @@ import { FormGroup, InputGroup } from '@blueprintjs/core';
|
|||||||
import { inputIntent } from 'utils';
|
import { inputIntent } from 'utils';
|
||||||
import { Row, Col, MoneyInputGroup } from 'components';
|
import { Row, Col, MoneyInputGroup } from 'components';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import { useAutofocus } from 'hooks';
|
||||||
import { decrementQuantity } from './utils';
|
import { decrementQuantity } from './utils';
|
||||||
import { toSafeNumber } from 'utils';
|
import { toSafeNumber } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrement adjustment fields.
|
||||||
|
*/
|
||||||
function DecrementAdjustmentFields() {
|
function DecrementAdjustmentFields() {
|
||||||
|
const decrementFieldRef = useAutofocus();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row className={'row--decrement-fields'}>
|
<Row className={'row--decrement-fields'}>
|
||||||
{/*------------ Quantity on hand -----------*/}
|
{/*------------ Quantity on hand -----------*/}
|
||||||
@@ -47,6 +53,7 @@ function DecrementAdjustmentFields() {
|
|||||||
value={field.value}
|
value={field.value}
|
||||||
allowDecimals={false}
|
allowDecimals={false}
|
||||||
allowNegativeValue={true}
|
allowNegativeValue={true}
|
||||||
|
inputRef={(ref) => (decrementFieldRef.current = ref)}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setFieldValue('quantity', value);
|
setFieldValue('quantity', value);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Field, FastField, ErrorMessage } from 'formik';
|
import { Field, FastField, ErrorMessage } from 'formik';
|
||||||
import { FormGroup, InputGroup } from '@blueprintjs/core';
|
import { FormGroup, InputGroup } from '@blueprintjs/core';
|
||||||
|
import { useAutofocus } from 'hooks';
|
||||||
import { Row, Col, MoneyInputGroup } from 'components';
|
import { Row, Col, MoneyInputGroup } from 'components';
|
||||||
import { inputIntent, toSafeNumber } from 'utils';
|
import { inputIntent, toSafeNumber } from 'utils';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { decrementQuantity, incrementQuantity } from './utils';
|
import { decrementQuantity, incrementQuantity } from './utils';
|
||||||
|
|
||||||
function IncrementAdjustmentFields() {
|
export default function IncrementAdjustmentFields() {
|
||||||
|
const incrementFieldRef = useAutofocus();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
{/*------------ Quantity on hand -----------*/}
|
{/*------------ Quantity on hand -----------*/}
|
||||||
@@ -47,6 +50,7 @@ function IncrementAdjustmentFields() {
|
|||||||
value={field.value}
|
value={field.value}
|
||||||
allowDecimals={false}
|
allowDecimals={false}
|
||||||
allowNegativeValue={true}
|
allowNegativeValue={true}
|
||||||
|
inputRef={(ref) => (incrementFieldRef.current = ref)}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setFieldValue('quantity', value);
|
setFieldValue('quantity', value);
|
||||||
}}
|
}}
|
||||||
@@ -131,5 +135,3 @@ function IncrementAdjustmentFields() {
|
|||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IncrementAdjustmentFields;
|
|
||||||
|
|||||||
@@ -2,29 +2,45 @@ import React from 'react';
|
|||||||
import { Intent, Button, Classes } from '@blueprintjs/core';
|
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { saveInvoke } from 'utils';
|
|
||||||
|
|
||||||
export default function InventoryAdjustmentFloatingActions({
|
import { useInventoryAdjContext } from './InventoryAdjustmentFormProvider';
|
||||||
onCloseClick,
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
onSubmitClick,
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inventory adjustment floating actions.
|
||||||
|
*/
|
||||||
|
function InventoryAdjustmentFloatingActions({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
const { isSubmitting } = useFormikContext();
|
// Formik context.
|
||||||
|
const { isSubmitting, submitForm } = useFormikContext();
|
||||||
|
|
||||||
|
// Inventory adjustment dialog context.
|
||||||
|
const { dialogName, setSubmitPayload } = useInventoryAdjContext();
|
||||||
|
|
||||||
|
// handle submit as draft button click.
|
||||||
const handleSubmitDraftBtnClick = (event) => {
|
const handleSubmitDraftBtnClick = (event) => {
|
||||||
saveInvoke(onSubmitClick, event, {
|
setSubmitPayload({ publish: false });
|
||||||
publish: false,
|
submitForm();
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle submit make adjustment button click.
|
||||||
const handleSubmitMakeAdjustmentBtnClick = (event) => {
|
const handleSubmitMakeAdjustmentBtnClick = (event) => {
|
||||||
saveInvoke(onSubmitClick, event, {
|
setSubmitPayload({ publish: true });
|
||||||
publish: true,
|
submitForm();
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// Handle close button click.
|
||||||
|
const handleCloseBtnClick = (event) => {
|
||||||
|
closeDialog(dialogName)
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
<Button onClick={onCloseClick} style={{ minWidth: '75px' }}>
|
<Button onClick={handleCloseBtnClick} style={{ minWidth: '75px' }}>
|
||||||
<T id={'close'} />
|
<T id={'close'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
@@ -49,3 +65,7 @@ export default function InventoryAdjustmentFloatingActions({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions
|
||||||
|
)(InventoryAdjustmentFloatingActions);
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import { omit, get } from 'lodash';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import 'style/pages/Items/ItemAdjustmentDialog.scss';
|
||||||
|
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
import { CreateInventoryAdjustmentFormSchema } from './InventoryAdjustmentForm.schema';
|
||||||
|
|
||||||
|
import InventoryAdjustmentFormContent from './InventoryAdjustmentFormContent';
|
||||||
|
import { useInventoryAdjContext } from './InventoryAdjustmentFormProvider';
|
||||||
|
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
const defaultInitialValues = {
|
||||||
|
date: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
|
type: 'decrement',
|
||||||
|
adjustment_account_id: '',
|
||||||
|
item_id: '',
|
||||||
|
reason: '',
|
||||||
|
cost: '',
|
||||||
|
quantity: '',
|
||||||
|
reference_no: '',
|
||||||
|
quantity_on_hand: '',
|
||||||
|
publish: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inventory adjustment form.
|
||||||
|
*/
|
||||||
|
function InventoryAdjustmentForm({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
dialogName,
|
||||||
|
item,
|
||||||
|
itemId,
|
||||||
|
submitPayload,
|
||||||
|
createInventoryAdjMutate,
|
||||||
|
} = useInventoryAdjContext();
|
||||||
|
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
// Initial form values.
|
||||||
|
const initialValues = {
|
||||||
|
...defaultInitialValues,
|
||||||
|
item_id: itemId,
|
||||||
|
quantity_on_hand: get(item, 'quantity_on_hand', 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handles the form submit.
|
||||||
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
|
const form = {
|
||||||
|
...omit(values, ['quantity_on_hand', 'new_quantity', 'action']),
|
||||||
|
publish: submitPayload.publish,
|
||||||
|
};
|
||||||
|
setSubmitting(true);
|
||||||
|
createInventoryAdjMutate(form)
|
||||||
|
.then(() => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_make_adjustment_has_been_created_successfully',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setSubmitting(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
validationSchema={CreateInventoryAdjustmentFormSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
>
|
||||||
|
<InventoryAdjustmentFormContent />
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(InventoryAdjustmentForm);
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'formik';
|
||||||
|
import InventoryAdjustmentFormDialogFields from './InventoryAdjustmentFormDialogFields';
|
||||||
|
import InventoryAdjustmentFloatingActions from './InventoryAdjustmentFloatingActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inventory adjustment form content.
|
||||||
|
*/
|
||||||
|
export default function InventoryAdjustmentFormContent() {
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<InventoryAdjustmentFormDialogFields />
|
||||||
|
<InventoryAdjustmentFloatingActions />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,142 +1,21 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
import React from 'react';
|
||||||
import { Intent } from '@blueprintjs/core';
|
|
||||||
import { Formik, Form } from 'formik';
|
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
import { useQuery, queryCache } from 'react-query';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { omit, get } from 'lodash';
|
|
||||||
|
|
||||||
import 'style/pages/Items/ItemAdjustmentDialog.scss';
|
import 'style/pages/Items/ItemAdjustmentDialog.scss';
|
||||||
|
|
||||||
import { AppToaster, DialogContent } from 'components';
|
import { InventoryAdjustmentFormProvider } from './InventoryAdjustmentFormProvider';
|
||||||
|
import InventoryAdjustmentForm from './InventoryAdjustmentForm';
|
||||||
import { CreateInventoryAdjustmentFormSchema } from './InventoryAdjustmentForm.schema';
|
|
||||||
|
|
||||||
import InventoryAdjustmentFormDialogFields from './InventoryAdjustmentFormDialogFields';
|
|
||||||
import InventoryAdjustmentFloatingActions from './InventoryAdjustmentFloatingActions';
|
|
||||||
|
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
|
||||||
import withInventoryAdjustmentActions from 'containers/Items/withInventoryAdjustmentActions';
|
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
|
||||||
import withItem from 'containers/Items/withItem';
|
|
||||||
import withItemsActions from 'containers/Items/withItemsActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
|
||||||
|
|
||||||
const defaultInitialValues = {
|
|
||||||
date: moment(new Date()).format('YYYY-MM-DD'),
|
|
||||||
type: 'decrement',
|
|
||||||
adjustment_account_id: '',
|
|
||||||
item_id: '',
|
|
||||||
reason: '',
|
|
||||||
cost: '',
|
|
||||||
quantity: '',
|
|
||||||
reference_no: '',
|
|
||||||
quantity_on_hand: '',
|
|
||||||
publish: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inventory adjustment form dialog content.
|
* Inventory adjustment form dialog content.
|
||||||
*/
|
*/
|
||||||
function InventoryAdjustmentFormDialogContent({
|
export default function InventoryAdjustmentFormDialogContent({
|
||||||
// #withDialogActions
|
// #ownProps
|
||||||
closeDialog,
|
|
||||||
|
|
||||||
// #withAccountsActions
|
|
||||||
requestFetchAccounts,
|
|
||||||
|
|
||||||
// #withInventoryAdjustmentActions
|
|
||||||
requestSubmitInventoryAdjustment,
|
|
||||||
|
|
||||||
// #withItemsActions
|
|
||||||
requestFetchItem,
|
|
||||||
|
|
||||||
// #withItem
|
|
||||||
item,
|
|
||||||
|
|
||||||
// #ownProp
|
|
||||||
itemId,
|
|
||||||
dialogName,
|
dialogName,
|
||||||
|
itemId
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const [submitPayload, setSubmitPayload] = useState({});
|
|
||||||
|
|
||||||
// Fetches accounts list.
|
|
||||||
const fetchAccount = useQuery('accounts-list', () => requestFetchAccounts());
|
|
||||||
|
|
||||||
// Fetches the item details.
|
|
||||||
const fetchItem = useQuery(['item', itemId],
|
|
||||||
(key, id) => requestFetchItem(id));
|
|
||||||
|
|
||||||
// Initial form values.
|
|
||||||
const initialValues = {
|
|
||||||
...defaultInitialValues,
|
|
||||||
item_id: itemId,
|
|
||||||
quantity_on_hand: get(item, 'quantity_on_hand', 0),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handles the form submit.
|
|
||||||
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
|
||||||
const form = {
|
|
||||||
...omit(values, ['quantity_on_hand', 'new_quantity', 'action']),
|
|
||||||
publish: submitPayload.publish,
|
|
||||||
};
|
|
||||||
const onSuccess = ({ response }) => {
|
|
||||||
closeDialog(dialogName);
|
|
||||||
queryCache.invalidateQueries('accounts-list');
|
|
||||||
queryCache.invalidateQueries('items-table');
|
|
||||||
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'the_make_adjustment_has_been_created_successfully',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const onError = (error) => {
|
|
||||||
setSubmitting(false);
|
|
||||||
};
|
|
||||||
requestSubmitInventoryAdjustment({ form }).then(onSuccess).catch(onError);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handles dialog close.
|
|
||||||
const handleCloseClick = useCallback(() => {
|
|
||||||
closeDialog(dialogName);
|
|
||||||
}, [closeDialog, dialogName]);
|
|
||||||
|
|
||||||
const handleSubmitClick = useCallback(
|
|
||||||
(event, payload) => {
|
|
||||||
setSubmitPayload({ ...payload });
|
|
||||||
},
|
|
||||||
[setSubmitPayload],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent isLoading={fetchAccount.isFetching || fetchItem.isFetching}>
|
<InventoryAdjustmentFormProvider itemId={itemId} dialogName={dialogName}>
|
||||||
<Formik
|
<InventoryAdjustmentForm />
|
||||||
validationSchema={CreateInventoryAdjustmentFormSchema}
|
</InventoryAdjustmentFormProvider>
|
||||||
initialValues={initialValues}
|
|
||||||
onSubmit={handleFormSubmit}
|
|
||||||
>
|
|
||||||
<Form>
|
|
||||||
<InventoryAdjustmentFormDialogFields dialogName={dialogName} />
|
|
||||||
<InventoryAdjustmentFloatingActions
|
|
||||||
onSubmitClick={handleSubmitClick}
|
|
||||||
onCloseClick={handleCloseClick}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
</Formik>
|
|
||||||
</DialogContent>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withInventoryAdjustmentActions,
|
|
||||||
withDialogActions,
|
|
||||||
withAccountsActions,
|
|
||||||
withItem(({ item }) => ({
|
|
||||||
item: item
|
|
||||||
})),
|
|
||||||
withItemsActions,
|
|
||||||
)(InventoryAdjustmentFormDialogContent);
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FastField, ErrorMessage, Field, useFormikContext } from 'formik';
|
import { FastField, ErrorMessage, Field } from 'formik';
|
||||||
import {
|
import {
|
||||||
Classes,
|
Classes,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { DateInput } from '@blueprintjs/datetime';
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
import { compose } from 'redux';
|
import { useAutofocus } from 'hooks';
|
||||||
import { ListSelect, FieldRequiredHint, Col, Row } from 'components';
|
import { ListSelect, FieldRequiredHint, Col, Row } from 'components';
|
||||||
import {
|
import {
|
||||||
inputIntent,
|
inputIntent,
|
||||||
@@ -23,18 +23,20 @@ import { CLASSES } from 'common/classes';
|
|||||||
import adjustmentType from 'common/adjustmentType';
|
import adjustmentType from 'common/adjustmentType';
|
||||||
|
|
||||||
import AccountsSuggestField from 'components/AccountsSuggestField';
|
import AccountsSuggestField from 'components/AccountsSuggestField';
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import { useInventoryAdjContext } from './InventoryAdjustmentFormProvider'
|
||||||
import { diffQuantity } from './utils';
|
import { diffQuantity } from './utils';
|
||||||
import InventoryAdjustmentQuantityFields from './InventoryAdjustmentQuantityFields';
|
import InventoryAdjustmentQuantityFields from './InventoryAdjustmentQuantityFields';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inventory adjustment form dialogs fields.
|
* Inventory adjustment form dialogs fields.
|
||||||
*/
|
*/
|
||||||
function InventoryAdjustmentFormDialogFields({
|
export default function InventoryAdjustmentFormDialogFields() {
|
||||||
//# withAccount
|
const dateFieldRef = useAutofocus();
|
||||||
accountsList,
|
|
||||||
}) {
|
// Inventory adjustment dialog context.
|
||||||
const { values } = useFormikContext();
|
const { accounts } = useInventoryAdjContext();
|
||||||
|
|
||||||
|
// Intl context.
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -62,6 +64,7 @@ function InventoryAdjustmentFormDialogFields({
|
|||||||
position: Position.BOTTOM,
|
position: Position.BOTTOM,
|
||||||
minimal: true,
|
minimal: true,
|
||||||
}}
|
}}
|
||||||
|
inputRef={(ref) => (dateFieldRef.current = ref)}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
@@ -115,7 +118,7 @@ function InventoryAdjustmentFormDialogFields({
|
|||||||
className={'form-group--adjustment-account'}
|
className={'form-group--adjustment-account'}
|
||||||
>
|
>
|
||||||
<AccountsSuggestField
|
<AccountsSuggestField
|
||||||
accounts={accountsList}
|
accounts={accounts}
|
||||||
onAccountSelected={(item) =>
|
onAccountSelected={(item) =>
|
||||||
form.setFieldValue('adjustment_account_id', item.id)
|
form.setFieldValue('adjustment_account_id', item.id)
|
||||||
}
|
}
|
||||||
@@ -159,9 +162,3 @@ function InventoryAdjustmentFormDialogFields({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withAccounts(({ accountsList }) => ({
|
|
||||||
accountsList,
|
|
||||||
})),
|
|
||||||
)(InventoryAdjustmentFormDialogFields);
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import React, { useState, createContext } from 'react';
|
||||||
|
import { DialogContent } from 'components';
|
||||||
|
import {
|
||||||
|
useItem,
|
||||||
|
useAccounts,
|
||||||
|
useCreateInventoryAdjustment,
|
||||||
|
} from 'hooks/query';
|
||||||
|
|
||||||
|
const InventoryAdjustmentContext = createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inventory adjustment dialog provider.
|
||||||
|
*/
|
||||||
|
function InventoryAdjustmentFormProvider({ itemId, dialogName, ...props }) {
|
||||||
|
// Fetches accounts list.
|
||||||
|
const { isFetching: isAccountsLoading, data: accounts } = useAccounts();
|
||||||
|
|
||||||
|
// Fetches the item details.
|
||||||
|
const { isFetching: isItemLoading, data: item } = useItem(itemId);
|
||||||
|
|
||||||
|
const {
|
||||||
|
mutateAsync: createInventoryAdjMutate,
|
||||||
|
} = useCreateInventoryAdjustment();
|
||||||
|
|
||||||
|
// Submit payload.
|
||||||
|
const [submitPayload, setSubmitPayload] = useState({});
|
||||||
|
|
||||||
|
// State provider.
|
||||||
|
const provider = {
|
||||||
|
itemId,
|
||||||
|
isAccountsLoading,
|
||||||
|
accounts,
|
||||||
|
isItemLoading,
|
||||||
|
item,
|
||||||
|
submitPayload,
|
||||||
|
dialogName,
|
||||||
|
|
||||||
|
createInventoryAdjMutate,
|
||||||
|
setSubmitPayload,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent isLoading={isAccountsLoading || isItemLoading}>
|
||||||
|
<InventoryAdjustmentContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useInventoryAdjContext = () => React.useContext(InventoryAdjustmentContext);
|
||||||
|
|
||||||
|
export { InventoryAdjustmentFormProvider, useInventoryAdjContext };
|
||||||
@@ -1,165 +1,111 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import {
|
import { useIntl } from 'react-intl';
|
||||||
Button,
|
import { Intent } from '@blueprintjs/core';
|
||||||
Classes,
|
import { Formik } from 'formik';
|
||||||
FormGroup,
|
|
||||||
InputGroup,
|
|
||||||
Intent,
|
|
||||||
TextArea,
|
|
||||||
MenuItem,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { ErrorMessage, Form, FastField } from 'formik';
|
|
||||||
import {
|
|
||||||
ListSelect,
|
|
||||||
AccountsSelectList,
|
|
||||||
FieldRequiredHint,
|
|
||||||
Hint,
|
|
||||||
} from 'components';
|
|
||||||
import { inputIntent } from 'utils';
|
|
||||||
|
|
||||||
import { useAutofocus } from 'hooks';
|
import { AppToaster } from 'components';
|
||||||
|
import { useItemCategoryContext } from './ItemCategoryProvider';
|
||||||
|
import { transformToForm } from 'utils';
|
||||||
|
import {
|
||||||
|
CreateItemCategoryFormSchema,
|
||||||
|
EditItemCategoryFormSchema,
|
||||||
|
} from './ItemCategoryForm.schema';
|
||||||
|
|
||||||
export default function ItemCategoryForm({
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
itemCategoryId,
|
import ItemCategoryFormContent from './ItemCategoryFormContent'
|
||||||
accountsList,
|
import { compose } from 'utils';
|
||||||
categoriesList,
|
|
||||||
isSubmitting,
|
const defaultInitialValues = {
|
||||||
onClose,
|
name: '',
|
||||||
|
description: '',
|
||||||
|
cost_account_id: '',
|
||||||
|
sell_account_id: '',
|
||||||
|
inventory_account_id: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item category form.
|
||||||
|
*/
|
||||||
|
function ItemCategoryForm({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
const categoryNameFieldRef = useAutofocus();
|
const { formatMessage } = useIntl();
|
||||||
|
const {
|
||||||
|
isNewMode,
|
||||||
|
itemCategory,
|
||||||
|
itemCategoryId,
|
||||||
|
dialogName,
|
||||||
|
createItemCategoryMutate,
|
||||||
|
editItemCategoryMutate,
|
||||||
|
} = useItemCategoryContext();
|
||||||
|
|
||||||
|
// Initial values.
|
||||||
|
const initialValues = useMemo(
|
||||||
|
() => ({
|
||||||
|
...defaultInitialValues,
|
||||||
|
...transformToForm(itemCategory, defaultInitialValues),
|
||||||
|
}),
|
||||||
|
[itemCategory],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Transformes response errors.
|
||||||
|
const transformErrors = (errors, { setErrors }) => {
|
||||||
|
if (errors.find((error) => error.type === 'CATEGORY_NAME_EXISTS')) {
|
||||||
|
setErrors({
|
||||||
|
name: formatMessage({ id: 'category_name_exists' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handles the form submit.
|
||||||
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
|
setSubmitting(true);
|
||||||
|
const form = { ...values };
|
||||||
|
|
||||||
|
// Handle close the dialog after success response.
|
||||||
|
const afterSubmit = () => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
// Handle the response success/
|
||||||
|
const onSuccess = ({ response }) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: isNewMode
|
||||||
|
? 'the_item_category_has_been_created_successfully'
|
||||||
|
: 'the_item_category_has_been_edited_successfully',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
afterSubmit(response);
|
||||||
|
};
|
||||||
|
// Handle the response error.
|
||||||
|
const onError = (errors) => {
|
||||||
|
transformErrors(errors, { setErrors });
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
if (isNewMode) {
|
||||||
|
createItemCategoryMutate(form).then(onSuccess).catch(onError);
|
||||||
|
} else {
|
||||||
|
editItemCategoryMutate([itemCategoryId, form])
|
||||||
|
.then(onSuccess)
|
||||||
|
.catch(onError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Formik
|
||||||
<div className={Classes.DIALOG_BODY}>
|
validationSchema={
|
||||||
{/* ----------- Category name ----------- */}
|
isNewMode ? CreateItemCategoryFormSchema : EditItemCategoryFormSchema
|
||||||
<FastField name={'name'}>
|
}
|
||||||
{({ field, field: { value }, meta: { error, touched } }) => (
|
initialValues={initialValues}
|
||||||
<FormGroup
|
onSubmit={handleFormSubmit}
|
||||||
label={<T id={'category_name'} />}
|
>
|
||||||
labelInfo={<FieldRequiredHint />}
|
<ItemCategoryFormContent />
|
||||||
className={'form-group--category-name'}
|
</Formik>
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name="name" />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<InputGroup
|
|
||||||
medium={true}
|
|
||||||
inputRef={(ref) => (categoryNameFieldRef.current = ref)}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
|
|
||||||
{/* ----------- Description ----------- */}
|
|
||||||
<FastField name={'description'}>
|
|
||||||
{({ field, field: { value }, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'description'} />}
|
|
||||||
className={'form-group--description'}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name="description" />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<TextArea growVertically={true} large={true} {...field} />
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ----------- Cost account ----------- */}
|
|
||||||
<FastField name={'cost_account_id'}>
|
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'cost_account'} />}
|
|
||||||
inline={true}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name="cost_account_id" />}
|
|
||||||
className={classNames(
|
|
||||||
'form-group--cost-account',
|
|
||||||
'form-group--select-list',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<AccountsSelectList
|
|
||||||
accounts={accountsList}
|
|
||||||
onAccountSelected={(account) => {
|
|
||||||
form.setFieldValue('cost_account_id', account.id);
|
|
||||||
}}
|
|
||||||
defaultSelectText={<T id={'select_account'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
filterByTypes={['cost_of_goods_sold']}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ----------- Sell account ----------- */}
|
|
||||||
<FastField name={'sell_account_id'}>
|
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'sell_account'} />}
|
|
||||||
inline={true}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name="sell_account_id" />}
|
|
||||||
className={classNames(
|
|
||||||
'form-group--sell-account',
|
|
||||||
'form-group--select-list',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<AccountsSelectList
|
|
||||||
accounts={accountsList}
|
|
||||||
onAccountSelected={(account) => {
|
|
||||||
form.setFieldValue('sell_account_id', account.id);
|
|
||||||
}}
|
|
||||||
defaultSelectText={<T id={'select_account'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
filterByTypes={['income']}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ----------- inventory account ----------- */}
|
|
||||||
<FastField name={'inventory_account_id'}>
|
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'inventory_account'} />}
|
|
||||||
inline={true}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name="inventory_account_id" />}
|
|
||||||
className={classNames(
|
|
||||||
'form-group--sell-account',
|
|
||||||
'form-group--select-list',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<AccountsSelectList
|
|
||||||
accounts={accountsList}
|
|
||||||
onAccountSelected={(account) => {
|
|
||||||
form.setFieldValue('inventory_account_id', account.id);
|
|
||||||
}}
|
|
||||||
defaultSelectText={<T id={'select_account'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
|
||||||
<Button onClick={onClose}>
|
|
||||||
<T id={'close'} />
|
|
||||||
</Button>
|
|
||||||
<Button intent={Intent.PRIMARY} type="submit" disabled={isSubmitting}>
|
|
||||||
{itemCategoryId ? <T id={'edit'} /> : <T id={'submit'} />}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
)(ItemCategoryForm);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'formik';
|
||||||
|
import ItemCategoryFormFields from './ItemCategoryFormFields';
|
||||||
|
import ItemCategoryFormFooter from './ItemCategoryFormFooter';
|
||||||
|
|
||||||
|
export default function ItemCategoryForm() {
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<ItemCategoryFormFields />
|
||||||
|
<ItemCategoryFormFooter />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,168 +1,23 @@
|
|||||||
import React, { useMemo, useCallback } from 'react';
|
import React from 'react';
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { ItemCategoryProvider } from './ItemCategoryProvider';
|
||||||
import { useQuery, queryCache } from 'react-query';
|
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
|
||||||
import { Formik } from 'formik';
|
|
||||||
import { AppToaster, DialogContent } from 'components';
|
|
||||||
|
|
||||||
import ItemCategoryForm from './ItemCategoryForm';
|
import ItemCategoryForm from './ItemCategoryForm';
|
||||||
import withItemCategories from 'containers/Items/withItemCategories';
|
|
||||||
import withItemCategoryDetail from 'containers/Items/withItemCategoryDetail';
|
|
||||||
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
|
|
||||||
|
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import 'style/pages/ItemCategory/ItemCategoryDialog.scss';
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EditItemCategoryFormSchema,
|
|
||||||
CreateItemCategoryFormSchema,
|
|
||||||
} from './itemCategoryForm.schema';
|
|
||||||
import { compose, transformToForm } from 'utils';
|
|
||||||
|
|
||||||
import 'style/pages/ItemCategory/ItemCategoryDialog.scss'
|
|
||||||
|
|
||||||
const defaultInitialValues = {
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
cost_account_id: '',
|
|
||||||
sell_account_id: '',
|
|
||||||
inventory_account_id: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item Category form dialog content.
|
* Item Category form dialog content.
|
||||||
*/
|
*/
|
||||||
|
export default function ItemCategoryFormDialogContent({
|
||||||
function ItemCategoryFormDialogContent({
|
|
||||||
// #withDialogActions
|
|
||||||
closeDialog,
|
|
||||||
|
|
||||||
// #withItemCategoryDetail
|
|
||||||
itemCategoryDetail,
|
|
||||||
|
|
||||||
// #withItemCategories
|
|
||||||
categoriesList,
|
|
||||||
|
|
||||||
// #withItemCategoriesActions
|
|
||||||
requestSubmitItemCategory,
|
|
||||||
requestEditItemCategory,
|
|
||||||
requestFetchItemCategories,
|
|
||||||
|
|
||||||
//# withAccount
|
|
||||||
accountsList,
|
|
||||||
|
|
||||||
// #withAccountsActions
|
|
||||||
requestFetchAccounts,
|
|
||||||
|
|
||||||
// #ownProp
|
// #ownProp
|
||||||
action,
|
|
||||||
itemCategoryId,
|
itemCategoryId,
|
||||||
dialogName,
|
dialogName,
|
||||||
}) {
|
}) {
|
||||||
const isNewMode = !itemCategoryId;
|
|
||||||
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
|
|
||||||
// Fetches categories list.
|
|
||||||
const fetchCategoriesList = useQuery(['items-categories-list'], () =>
|
|
||||||
requestFetchItemCategories(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fetches accounts list.
|
|
||||||
const fetchAccountsList = useQuery('accounts-list', () =>
|
|
||||||
requestFetchAccounts(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const initialValues = useMemo(
|
|
||||||
() => ({
|
|
||||||
...defaultInitialValues,
|
|
||||||
...transformToForm(itemCategoryDetail, defaultInitialValues),
|
|
||||||
}),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const transformErrors = (errors, { setErrors }) => {
|
|
||||||
if (errors.find((error) => error.type === 'CATEGORY_NAME_EXISTS')) {
|
|
||||||
setErrors({
|
|
||||||
name: formatMessage({ id: 'category_name_exists' }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handles the form submit.
|
|
||||||
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
|
||||||
setSubmitting(true);
|
|
||||||
const form = { ...values };
|
|
||||||
const afterSubmit = () => {
|
|
||||||
closeDialog(dialogName);
|
|
||||||
queryCache.invalidateQueries('items-categories-list');
|
|
||||||
queryCache.invalidateQueries('accounts-list');
|
|
||||||
};
|
|
||||||
const onSuccess = ({ response }) => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: isNewMode
|
|
||||||
? 'the_item_category_has_been_created_successfully'
|
|
||||||
: 'the_item_category_has_been_edited_successfully',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
afterSubmit(response);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onError = (errors) => {
|
|
||||||
transformErrors(errors, { setErrors });
|
|
||||||
setSubmitting(false);
|
|
||||||
};
|
|
||||||
if (isNewMode) {
|
|
||||||
requestSubmitItemCategory(form).then(onSuccess).catch(onError);
|
|
||||||
} else {
|
|
||||||
requestEditItemCategory(itemCategoryId, form)
|
|
||||||
.then(onSuccess)
|
|
||||||
.catch(onError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handles dialog close.
|
|
||||||
const handleClose = useCallback(() => {
|
|
||||||
closeDialog(dialogName);
|
|
||||||
}, [closeDialog, dialogName]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent
|
<ItemCategoryProvider
|
||||||
isLoading={fetchCategoriesList.isFetching || fetchAccountsList.isFetching}
|
itemCategoryId={itemCategoryId}
|
||||||
|
dialogName={dialogName}
|
||||||
>
|
>
|
||||||
<Formik
|
<ItemCategoryForm />
|
||||||
validationSchema={
|
</ItemCategoryProvider>
|
||||||
isNewMode ? CreateItemCategoryFormSchema : EditItemCategoryFormSchema
|
|
||||||
}
|
|
||||||
initialValues={initialValues}
|
|
||||||
onSubmit={handleFormSubmit}
|
|
||||||
>
|
|
||||||
{({ isSubmitting }) => (
|
|
||||||
<ItemCategoryForm
|
|
||||||
itemCategoryId={itemCategoryId}
|
|
||||||
accountsList={accountsList}
|
|
||||||
categoriesList={categoriesList}
|
|
||||||
isSubmitting={isSubmitting}
|
|
||||||
onClose={handleClose}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Formik>
|
|
||||||
</DialogContent>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withDialogActions,
|
|
||||||
withItemCategoryDetail(),
|
|
||||||
withItemCategories(({ categoriesList }) => ({
|
|
||||||
categoriesList,
|
|
||||||
})),
|
|
||||||
withAccounts(({ accountsList }) => ({
|
|
||||||
accountsList,
|
|
||||||
})),
|
|
||||||
withItemCategoriesActions,
|
|
||||||
withAccountsActions,
|
|
||||||
)(ItemCategoryFormDialogContent);
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Classes, FormGroup, InputGroup, TextArea } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import { ErrorMessage, FastField } from 'formik';
|
||||||
|
|
||||||
|
import { useAutofocus } from 'hooks';
|
||||||
|
import { FieldRequiredHint } from 'components';
|
||||||
|
import { inputIntent } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item category form fields.
|
||||||
|
*/
|
||||||
|
export default function ItemCategoryFormFields() {
|
||||||
|
const categoryNameFieldRef = useAutofocus();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
{/* ----------- Category name ----------- */}
|
||||||
|
<FastField name={'name'}>
|
||||||
|
{({ field, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'category_name'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
className={'form-group--category-name'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="name" />}
|
||||||
|
inline={true}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
medium={true}
|
||||||
|
inputRef={(ref) => (categoryNameFieldRef.current = ref)}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
{/* ----------- Description ----------- */}
|
||||||
|
<FastField name={'description'}>
|
||||||
|
{({ field, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
className={'form-group--description'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="description" />}
|
||||||
|
inline={true}
|
||||||
|
>
|
||||||
|
<TextArea growVertically={true} large={true} {...field} />
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Classes, Button, Intent } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { useItemCategoryContext } from './ItemCategoryProvider';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item category form footer.
|
||||||
|
*/
|
||||||
|
function ItemCategoryFormFooter({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
// Item category context.
|
||||||
|
const { isNewMode, dialogName } = useItemCategoryContext();
|
||||||
|
|
||||||
|
// Formik context.
|
||||||
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
// Handle close button click.
|
||||||
|
const handleCloseBtnClick = () => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
<Button onClick={handleCloseBtnClick}>
|
||||||
|
<T id={'close'} />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button intent={Intent.PRIMARY} type="submit" disabled={isSubmitting}>
|
||||||
|
{isNewMode ? <T id={'submit'} /> : <T id={'edit'} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default compose(withDialogActions)(ItemCategoryFormFooter);
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import React, { createContext } from 'react';
|
||||||
|
import { DialogContent } from 'components';
|
||||||
|
import {
|
||||||
|
useItemCategory,
|
||||||
|
useEditItemCategory,
|
||||||
|
useCreateItemCategory,
|
||||||
|
} from 'hooks/query';
|
||||||
|
|
||||||
|
const ItemCategoryContext = createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts chart data provider.
|
||||||
|
*/
|
||||||
|
function ItemCategoryProvider({ itemCategoryId, dialogName, ...props }) {
|
||||||
|
const { data: itemCategory, isFetching: isItemCategoryLoading } = useItemCategory(
|
||||||
|
itemCategoryId,
|
||||||
|
{
|
||||||
|
enabled: !!itemCategoryId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Create and edit item category mutations.
|
||||||
|
const { mutateAsync: createItemCategoryMutate } = useCreateItemCategory();
|
||||||
|
const { mutateAsync: editItemCategoryMutate } = useEditItemCategory();
|
||||||
|
|
||||||
|
// Detarmines whether the new mode form.
|
||||||
|
const isNewMode = !itemCategoryId;
|
||||||
|
const isEditMode = !isNewMode;
|
||||||
|
|
||||||
|
// Provider state.
|
||||||
|
const provider = {
|
||||||
|
itemCategoryId,
|
||||||
|
dialogName,
|
||||||
|
|
||||||
|
itemCategory,
|
||||||
|
isItemCategoryLoading,
|
||||||
|
|
||||||
|
createItemCategoryMutate,
|
||||||
|
editItemCategoryMutate,
|
||||||
|
|
||||||
|
isNewMode,
|
||||||
|
isEditMode
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent
|
||||||
|
isLoading={isItemCategoryLoading}
|
||||||
|
name={'item-category-form'}
|
||||||
|
>
|
||||||
|
<ItemCategoryContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useItemCategoryContext = () =>
|
||||||
|
React.useContext(ItemCategoryContext);
|
||||||
|
|
||||||
|
export { ItemCategoryProvider, useItemCategoryContext };
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { lazy } from 'react';
|
import React, { lazy } from 'react';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { Dialog, DialogSuspense } from 'components';
|
import { Dialog, DialogSuspense } from 'components';
|
||||||
|
|
||||||
import withDialogRedux from 'components/DialogReduxConnect';
|
import withDialogRedux from 'components/DialogReduxConnect';
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ const Schema = Yup.object().shape({
|
|||||||
.required()
|
.required()
|
||||||
.max(DATATYPES_LENGTH.STRING)
|
.max(DATATYPES_LENGTH.STRING)
|
||||||
.label(formatMessage({ id: 'category_name_' })),
|
.label(formatMessage({ id: 'category_name_' })),
|
||||||
cost_account_id: Yup.number().nullable(),
|
|
||||||
sell_account_id: Yup.number().nullable(),
|
|
||||||
inventory_account_id: Yup.number().nullable(),
|
|
||||||
description: Yup.string().trim().max(DATATYPES_LENGTH.TEXT).nullable(),
|
description: Yup.string().trim().max(DATATYPES_LENGTH.TEXT).nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user