refactoring: migrating to react-query to manage service-side state.

This commit is contained in:
a.bouhuolia
2021-02-07 08:10:21 +02:00
parent e093be0663
commit adac2386bb
284 changed files with 8255 additions and 6610 deletions

View File

@@ -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",

View 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,
},
];

View File

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

View File

@@ -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'}

View 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();
}
};
}

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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: {},
}; };

View File

@@ -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">

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

View File

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

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

View File

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

View 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}%` }} />;
}

View File

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

View File

@@ -171,7 +171,8 @@ export default [
], ],
}, },
{ {
divider: true, text: <T id={'system'} />,
label: true,
}, },
{ {
text: <T id={'preferences'} />, text: <T id={'preferences'} />,

View File

@@ -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 (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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 };

View File

@@ -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} />}

View 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>
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -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,

View 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 };

View File

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

View File

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

View File

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

View File

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

View File

@@ -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({

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,

View File

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

View File

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

View File

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

View File

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

View 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 };

View File

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

View File

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

View 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 };

View File

@@ -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'}

View File

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

View File

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

View File

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

View File

@@ -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,
}) { }) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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