mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
chrone: sperate client and server to different repos.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import Icon from 'components/Icon';
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
NavbarGroup,
|
||||
Intent,
|
||||
NavbarDivider,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
|
||||
import { safeCallback } from 'utils';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { useAccountDrawerContext } from './AccountDrawerProvider';
|
||||
|
||||
/**
|
||||
* Account drawer action bar.
|
||||
*/
|
||||
function AccountDrawerActionBar({
|
||||
// #withDialog
|
||||
openDialog,
|
||||
|
||||
// #withAlertsDialog
|
||||
openAlert,
|
||||
}) {
|
||||
// Account drawer context.
|
||||
const { account } = useAccountDrawerContext();
|
||||
|
||||
// Handle new child button click.
|
||||
const onNewChildAccount = () => {
|
||||
openDialog('account-form', {
|
||||
action: 'new_child',
|
||||
parentAccountId: account.id,
|
||||
accountType: account.account_type,
|
||||
});
|
||||
};
|
||||
|
||||
// Handle edit account action.
|
||||
const onEditAccount = () => {
|
||||
openDialog('account-form', { action: 'edit', id: account.id });
|
||||
};
|
||||
|
||||
// Handle delete action account.
|
||||
const onDeleteAccount = () => {
|
||||
openAlert('account-delete', { accountId: account.id });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={<T id={'edit_account'} />}
|
||||
onClick={safeCallback(onEditAccount)}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="plus" />}
|
||||
text={<T id={'new_child_account'} />}
|
||||
onClick={safeCallback(onNewChildAccount)}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDeleteAccount)}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
withAlertsActions,
|
||||
)(AccountDrawerActionBar);
|
||||
24
src/containers/Drawers/AccountDrawer/AccountDrawerContent.js
Normal file
24
src/containers/Drawers/AccountDrawer/AccountDrawerContent.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { DrawerBody } from 'components';
|
||||
|
||||
import 'style/components/Drawers/AccountDrawer.scss';
|
||||
|
||||
import { AccountDrawerProvider } from './AccountDrawerProvider';
|
||||
import AccountDrawerDetails from './AccountDrawerDetails';
|
||||
|
||||
/**
|
||||
* Account drawer content.
|
||||
*/
|
||||
export default function AccountDrawerContent({
|
||||
// #ownProp
|
||||
accountId,
|
||||
name,
|
||||
}) {
|
||||
return (
|
||||
<AccountDrawerProvider name={name} accountId={accountId}>
|
||||
<DrawerBody>
|
||||
<AccountDrawerDetails />
|
||||
</DrawerBody>
|
||||
</AccountDrawerProvider>
|
||||
);
|
||||
}
|
||||
23
src/containers/Drawers/AccountDrawer/AccountDrawerDetails.js
Normal file
23
src/containers/Drawers/AccountDrawer/AccountDrawerDetails.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Card } from 'components';
|
||||
|
||||
import AccountDrawerActionBar from './AccountDrawerActionBar';
|
||||
import AccountDrawerHeader from './AccountDrawerHeader';
|
||||
import AccountDrawerTable from './AccountDrawerTable';
|
||||
|
||||
/**
|
||||
* Account view details.
|
||||
*/
|
||||
export default function AccountDrawerDetails() {
|
||||
return (
|
||||
<div className={'account-drawer'}>
|
||||
<AccountDrawerActionBar />
|
||||
|
||||
<Card className={'card-header'}>
|
||||
<AccountDrawerHeader />
|
||||
</Card>
|
||||
<AccountDrawerTable />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
53
src/containers/Drawers/AccountDrawer/AccountDrawerHeader.js
Normal file
53
src/containers/Drawers/AccountDrawer/AccountDrawerHeader.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import { defaultTo } from 'lodash';
|
||||
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import { Icon, Money, DetailsMenu, DetailItem } from 'components';
|
||||
import { useAccountDrawerContext } from './AccountDrawerProvider';
|
||||
|
||||
/**
|
||||
* Account drawer header.
|
||||
*/
|
||||
export default function AccountDrawerHeader() {
|
||||
const { account } = useAccountDrawerContext();
|
||||
return (
|
||||
<div className={'account-drawer__content-header'}>
|
||||
<DetailsMenu>
|
||||
<DetailItem
|
||||
name={'closing-balance'}
|
||||
label={<T id={'closing_balance'} />}
|
||||
>
|
||||
<h3 class={'big-number'}>{account.formatted_amount}</h3>
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'account-type'} label={<T id={'account_type'} />}>
|
||||
{account.account_type_label}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'account-normal'} label={<T id={'account_normal'} />}>
|
||||
{account.account_normal}
|
||||
<Icon
|
||||
iconSize={14}
|
||||
icon={`arrow-${
|
||||
account.account_normal === 'credit' ? 'down' : 'up'
|
||||
}`}
|
||||
/>
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'code'} label={<T id={'code'} />}>
|
||||
{account.code}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'currency'} label={<T id={'currency'} />}>
|
||||
{account.currency_code}
|
||||
</DetailItem>
|
||||
</DetailsMenu>
|
||||
|
||||
<DetailsMenu direction={'horizantal'}>
|
||||
<DetailItem name={'description'} label={<T id={'description'} />}>
|
||||
{defaultTo(account.description, '--')}
|
||||
</DetailItem>
|
||||
</DetailsMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import { useAccount, useAccountTransactions } from 'hooks/query';
|
||||
import { DrawerHeaderContent, DrawerLoading } from 'components';
|
||||
|
||||
const AccountDrawerContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Account drawer provider.
|
||||
*/
|
||||
function AccountDrawerProvider({ accountId, name, ...props }) {
|
||||
// Fetches the specific account details.
|
||||
const { data: account, isLoading: isAccountLoading } = useAccount(accountId, {
|
||||
enabled: !!accountId,
|
||||
});
|
||||
|
||||
// Load the specific account transactions.
|
||||
const { data: accounts, isLoading: isAccountsLoading } =
|
||||
useAccountTransactions(accountId, {
|
||||
enabled: !!accountId,
|
||||
});
|
||||
|
||||
// Drawer title.
|
||||
const drawerTitle = `${account.name} ${account.code}`;
|
||||
|
||||
// Provider.
|
||||
const provider = {
|
||||
accountId,
|
||||
account,
|
||||
accounts,
|
||||
drawerName: name,
|
||||
};
|
||||
|
||||
return (
|
||||
<DrawerLoading loading={isAccountLoading || isAccountsLoading}>
|
||||
<DrawerHeaderContent name={'account-drawer'} title={drawerTitle} />
|
||||
<AccountDrawerContext.Provider value={provider} {...props} />
|
||||
</DrawerLoading>
|
||||
);
|
||||
}
|
||||
|
||||
const useAccountDrawerContext = () => React.useContext(AccountDrawerContext);
|
||||
|
||||
export { AccountDrawerProvider, useAccountDrawerContext };
|
||||
51
src/containers/Drawers/AccountDrawer/AccountDrawerTable.js
Normal file
51
src/containers/Drawers/AccountDrawer/AccountDrawerTable.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { useAccountDrawerContext } from './AccountDrawerProvider';
|
||||
|
||||
import { DataTable, If } from 'components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { useAccountReadEntriesColumns } from './utils';
|
||||
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
|
||||
/**
|
||||
* account drawer table.
|
||||
*/
|
||||
function AccountDrawerTable({ closeDrawer }) {
|
||||
const {
|
||||
account,
|
||||
accounts,
|
||||
drawerName,
|
||||
} = useAccountDrawerContext();
|
||||
|
||||
// Account read-only entries table columns.
|
||||
const columns = useAccountReadEntriesColumns();
|
||||
|
||||
// Handle view more link click.
|
||||
const handleLinkClick = () => {
|
||||
closeDrawer(drawerName);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'account-drawer__table'}>
|
||||
<DataTable columns={columns} data={accounts} payload={{ account }}/>
|
||||
|
||||
<If condition={accounts.length > 0}>
|
||||
<div class="account-drawer__table-footer">
|
||||
<Link
|
||||
to={`/financial-reports/general-ledger`}
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
←{intl.get('view_more_transactions')}
|
||||
</Link>
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDrawerActions)(AccountDrawerTable);
|
||||
32
src/containers/Drawers/AccountDrawer/index.js
Normal file
32
src/containers/Drawers/AccountDrawer/index.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import React, { lazy } from 'react';
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const AccountDrawerContent = lazy(() => import('./AccountDrawerContent'));
|
||||
|
||||
/**
|
||||
* Account drawer.
|
||||
*/
|
||||
function AccountDrawer({
|
||||
name,
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { accountId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
name={name}
|
||||
style={{ minWidth: '700px', maxWidth: '900px' }}
|
||||
size={'65%'}
|
||||
>
|
||||
<DrawerSuspense>
|
||||
<AccountDrawerContent name={name} accountId={accountId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDrawers())(AccountDrawer);
|
||||
69
src/containers/Drawers/AccountDrawer/utils.js
Normal file
69
src/containers/Drawers/AccountDrawer/utils.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import intl from 'react-intl-universal';
|
||||
import React from 'react';
|
||||
|
||||
import { FormatDateCell } from '../../../components';
|
||||
import { isBlank } from 'utils';
|
||||
|
||||
/**
|
||||
* Debit/credit table cell.
|
||||
*/
|
||||
function DebitCreditTableCell({ value, payload: { account } }) {
|
||||
return !isBlank(value) && value !== 0 ? account.formatted_amount : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Running balance table cell.
|
||||
*/
|
||||
function RunningBalanceTableCell({ value, payload: { account } }) {
|
||||
return account.formatted_amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve entries columns of read-only account view.
|
||||
*/
|
||||
export const useAccountReadEntriesColumns = () =>
|
||||
React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('transaction_date'),
|
||||
accessor: 'date',
|
||||
Cell: FormatDateCell,
|
||||
width: 110,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('transaction_type'),
|
||||
accessor: 'reference_type_formatted',
|
||||
width: 100,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('credit'),
|
||||
accessor: 'credit',
|
||||
Cell: DebitCreditTableCell,
|
||||
width: 80,
|
||||
className: 'credit',
|
||||
align: 'right',
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('debit'),
|
||||
accessor: 'debit',
|
||||
Cell: DebitCreditTableCell,
|
||||
width: 80,
|
||||
className: 'debit',
|
||||
align: 'right',
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('running_balance'),
|
||||
Cell: RunningBalanceTableCell,
|
||||
accessor: 'running_balance',
|
||||
width: 110,
|
||||
className: 'running_balance',
|
||||
align: 'right',
|
||||
textOverview: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
74
src/containers/Drawers/BillDrawer/BillDetailActionsBar.js
Normal file
74
src/containers/Drawers/BillDrawer/BillDetailActionsBar.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Button,
|
||||
NavbarGroup,
|
||||
Classes,
|
||||
NavbarDivider,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
|
||||
import { useBillDrawerContext } from './BillDrawerProvider';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { Icon, FormattedMessage as T } from 'components';
|
||||
|
||||
import { safeCallback, compose } from 'utils';
|
||||
|
||||
function BillDetailActionsBar({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
const { billId } = useBillDrawerContext();
|
||||
|
||||
// Handle edit bill.
|
||||
const onEditBill = () => {
|
||||
history.push(`/bills/${billId}/edit`);
|
||||
closeDrawer('bill-drawer');
|
||||
};
|
||||
|
||||
// Handle delete bill.
|
||||
const onDeleteBill = () => {
|
||||
openAlert('bill-delete', { billId });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={<T id={'edit_bill'} />}
|
||||
onClick={safeCallback(onEditBill)}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDeleteBill)}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
withDrawerActions,
|
||||
withAlertsActions,
|
||||
)(BillDetailActionsBar);
|
||||
41
src/containers/Drawers/BillDrawer/BillDetailFooter.js
Normal file
41
src/containers/Drawers/BillDrawer/BillDetailFooter.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { FormatNumber, T, TotalLines, TotalLine } from '../../../components';
|
||||
import { useBillDrawerContext } from './BillDrawerProvider';
|
||||
|
||||
import BillDrawerCls from 'style/components/Drawers/BillDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Bill read-only details footer.
|
||||
*/
|
||||
export function BillDetailFooter() {
|
||||
const { bill } = useBillDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(BillDrawerCls.detail_panel_footer)}>
|
||||
<TotalLines>
|
||||
<TotalLine
|
||||
title={<T id={'bill.details.subtotal'} />}
|
||||
value={<FormatNumber value={bill.amount} />}
|
||||
className={BillDrawerCls.total_line_subtotal}
|
||||
/>
|
||||
<TotalLine
|
||||
title={<T id={'bill.details.total'} />}
|
||||
value={bill.formatted_amount}
|
||||
className={BillDrawerCls.total_line_total}
|
||||
/>
|
||||
<TotalLine
|
||||
title={<T id={'bill.details.payment_amount'} />}
|
||||
value={bill.formatted_payment_amount}
|
||||
className={BillDrawerCls.total_line_payment}
|
||||
/>
|
||||
<TotalLine
|
||||
title={<T id={'bill.details.due_amount'} />}
|
||||
value={bill.formatted_due_amount}
|
||||
className={BillDrawerCls.total_line_dueAmount}
|
||||
/>
|
||||
</TotalLines>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
58
src/containers/Drawers/BillDrawer/BillDetailHeader.js
Normal file
58
src/containers/Drawers/BillDrawer/BillDetailHeader.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { defaultTo } from 'lodash';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { FormatDate, DetailsMenu, DetailItem } from 'components';
|
||||
|
||||
import { useBillDrawerContext } from './BillDrawerProvider';
|
||||
import BillDrawerCls from 'style/components/Drawers/BillDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Bill detail header.
|
||||
*/
|
||||
export default function BillDetailHeader() {
|
||||
const { bill } = useBillDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(BillDrawerCls.detail_panel_header)}>
|
||||
<DetailsMenu>
|
||||
<DetailItem
|
||||
label={intl.get('amount')}
|
||||
children={<h3 class="big-number">{bill.formatted_amount}</h3>}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('bill.details.bill_number')}
|
||||
children={defaultTo(bill.bill_number, '-')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('bill_date')}
|
||||
children={<FormatDate value={bill.bill_date} />}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('vendor_name')}
|
||||
children={bill.vendor?.display_name}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('due_date')}
|
||||
children={<FormatDate value={bill.due_date} />}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
|
||||
<DetailsMenu direction={'horizantal'} minLabelSize={'140px'}>
|
||||
<DetailItem
|
||||
label={intl.get('due_amount')}
|
||||
children={bill.formatted_due_amount}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('reference')}
|
||||
children={defaultTo(bill.reference_no, '--')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('bill.details.created_at')}
|
||||
children={<FormatDate value={bill.created_at} />}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
29
src/containers/Drawers/BillDrawer/BillDetailTab.js
Normal file
29
src/containers/Drawers/BillDrawer/BillDetailTab.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { Card } from 'components';
|
||||
|
||||
import BillDetailActionsBar from './BillDetailActionsBar';
|
||||
import BillDetailHeader from './BillDetailHeader';
|
||||
import BillDetailTable from './BillDetailTable';
|
||||
import { BillDetailFooter } from './BillDetailFooter'
|
||||
|
||||
import BillDrawerCls from 'style/components/Drawers/BillDrawer.module.scss';
|
||||
|
||||
|
||||
/**
|
||||
* Bill detail panel tab.
|
||||
*/
|
||||
export default function BillDetailTab() {
|
||||
return (
|
||||
<div className={clsx(BillDrawerCls.detail_panel)}>
|
||||
<BillDetailActionsBar />
|
||||
|
||||
<Card>
|
||||
<BillDetailHeader />
|
||||
<BillDetailTable />
|
||||
<BillDetailFooter />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
26
src/containers/Drawers/BillDrawer/BillDetailTable.js
Normal file
26
src/containers/Drawers/BillDrawer/BillDetailTable.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { DataTable } from 'components';
|
||||
|
||||
import { useBillDrawerContext } from './BillDrawerProvider';
|
||||
import { useBillReadonlyEntriesTableColumns } from './utils';
|
||||
|
||||
import BillDrawerCls from 'style/components/Drawers/BillDrawer.module.scss';
|
||||
|
||||
export default function BillDetailTable() {
|
||||
const { bill: { entries } } = useBillDrawerContext();
|
||||
|
||||
// Retrieve bill readonly entries table columns.
|
||||
const columns = useBillReadonlyEntriesTableColumns();
|
||||
|
||||
return (
|
||||
<div className={clsx(BillDrawerCls.detail_panel_table)}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={entries}
|
||||
className={'table-constrant'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
src/containers/Drawers/BillDrawer/BillDrawerAlerts.js
Normal file
13
src/containers/Drawers/BillDrawer/BillDrawerAlerts.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import BillLocatedLandedCostDeleteAlert from 'containers/Alerts/Bills/BillLocatedLandedCostDeleteAlert';
|
||||
|
||||
/**
|
||||
* Bill drawer alert.
|
||||
*/
|
||||
export default function BillDrawerAlerts() {
|
||||
return (
|
||||
<div class="bills-alerts">
|
||||
<BillLocatedLandedCostDeleteAlert name="bill-located-cost-delete" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
24
src/containers/Drawers/BillDrawer/BillDrawerContent.js
Normal file
24
src/containers/Drawers/BillDrawer/BillDrawerContent.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { DrawerBody } from 'components';
|
||||
|
||||
import 'style/components/Drawers/ViewDetail/ViewDetail.scss';
|
||||
|
||||
import { BillDrawerProvider } from './BillDrawerProvider';
|
||||
import BillDrawerDetails from './BillDrawerDetails';
|
||||
// import BillDrawerAlerts from './BillDrawerAlerts';
|
||||
|
||||
/**
|
||||
* Bill drawer content.
|
||||
*/
|
||||
export default function BillDrawerContent({
|
||||
// #ownProp
|
||||
billId,
|
||||
}) {
|
||||
return (
|
||||
<BillDrawerProvider billId={billId}>
|
||||
<DrawerBody>
|
||||
<BillDrawerDetails />
|
||||
</DrawerBody>
|
||||
</BillDrawerProvider>
|
||||
);
|
||||
}
|
||||
44
src/containers/Drawers/BillDrawer/BillDrawerDetails.js
Normal file
44
src/containers/Drawers/BillDrawer/BillDrawerDetails.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { Tab } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { DrawerMainTabs } from 'components';
|
||||
|
||||
import BillDetailTab from './BillDetailTab';
|
||||
import LocatedLandedCostTable from './LocatedLandedCostTable';
|
||||
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
|
||||
import { useBillDrawerContext } from './BillDrawerProvider';
|
||||
|
||||
import BillDrawerCls from 'style/components/Drawers/BillDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Bill view details.
|
||||
*/
|
||||
export default function BillDrawerDetails() {
|
||||
const {
|
||||
data: { transactions },
|
||||
} = useBillDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(BillDrawerCls.root)}>
|
||||
<DrawerMainTabs defaultSelectedTabId="details">
|
||||
<Tab
|
||||
title={intl.get('details')}
|
||||
id={'details'}
|
||||
panel={<BillDetailTab />}
|
||||
/>
|
||||
<Tab
|
||||
title={intl.get('journal_entries')}
|
||||
id={'journal_entries'}
|
||||
panel={<JournalEntriesTable transactions={transactions} />}
|
||||
/>
|
||||
<Tab
|
||||
title={intl.get('located_landed_cost')}
|
||||
id={'landed_cost'}
|
||||
panel={<LocatedLandedCostTable />}
|
||||
/>
|
||||
</DrawerMainTabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
59
src/containers/Drawers/BillDrawer/BillDrawerProvider.js
Normal file
59
src/containers/Drawers/BillDrawer/BillDrawerProvider.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DrawerHeaderContent, DrawerLoading } from 'components';
|
||||
import {
|
||||
useBill,
|
||||
useTransactionsByReference,
|
||||
useBillLocatedLandedCost,
|
||||
} from 'hooks/query';
|
||||
|
||||
const BillDrawerContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Bill drawer provider.
|
||||
*/
|
||||
function BillDrawerProvider({ billId, ...props }) {
|
||||
// Handle fetch bill details.
|
||||
const { isLoading: isBillLoading, data: bill } = useBill(billId, {
|
||||
enabled: !!billId,
|
||||
});
|
||||
|
||||
// Handle fetch transaction by reference.
|
||||
const { data, isLoading: isTransactionLoading } = useTransactionsByReference(
|
||||
{
|
||||
reference_id: billId,
|
||||
reference_type: 'Bill',
|
||||
},
|
||||
{ enabled: !!billId },
|
||||
);
|
||||
|
||||
// Handle fetch bill located landed cost transaction.
|
||||
const { isLoading: isLandedCostLoading, data: transactions } =
|
||||
useBillLocatedLandedCost(billId, {
|
||||
enabled: !!billId,
|
||||
});
|
||||
|
||||
//provider.
|
||||
const provider = {
|
||||
transactions,
|
||||
billId,
|
||||
data,
|
||||
bill,
|
||||
};
|
||||
|
||||
const loading = isLandedCostLoading || isTransactionLoading || isBillLoading;
|
||||
|
||||
return (
|
||||
<DrawerLoading loading={loading}>
|
||||
<DrawerHeaderContent
|
||||
name="bill-drawer"
|
||||
title={intl.get('bill_details')}
|
||||
/>
|
||||
<BillDrawerContext.Provider value={provider} {...props} />
|
||||
</DrawerLoading>
|
||||
);
|
||||
}
|
||||
|
||||
const useBillDrawerContext = () => React.useContext(BillDrawerContext);
|
||||
|
||||
export { BillDrawerProvider, useBillDrawerContext };
|
||||
91
src/containers/Drawers/BillDrawer/LocatedLandedCostTable.js
Normal file
91
src/containers/Drawers/BillDrawer/LocatedLandedCostTable.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import { DataTable, Card } from 'components';
|
||||
import { Button, Classes, NavbarGroup } from '@blueprintjs/core';
|
||||
|
||||
import { useLocatedLandedCostColumns, ActionsMenu } from './components';
|
||||
import { useBillDrawerContext } from './BillDrawerProvider';
|
||||
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import Icon from 'components/Icon';
|
||||
|
||||
/**
|
||||
* Located landed cost table.
|
||||
*/
|
||||
function LocatedLandedCostTable({
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
}) {
|
||||
const columns = useLocatedLandedCostColumns();
|
||||
const { transactions, billId } = useBillDrawerContext();
|
||||
|
||||
// Handle the transaction delete action.
|
||||
const handleDeleteTransaction = ({ id }) => {
|
||||
openAlert('bill-located-cost-delete', { BillId: id });
|
||||
};
|
||||
|
||||
// Handle allocate landed cost button click.
|
||||
const handleAllocateCostClick = () => {
|
||||
openDialog('allocate-landed-cost', { billId });
|
||||
};
|
||||
|
||||
// Handle from transaction link click.
|
||||
const handleFromTransactionClick = (original) => {
|
||||
const { from_transaction_type, from_transaction_id } = original;
|
||||
|
||||
switch (from_transaction_type) {
|
||||
case 'Expense':
|
||||
openDrawer('expense-drawer', { expenseId: from_transaction_id });
|
||||
break;
|
||||
|
||||
case 'Bill':
|
||||
default:
|
||||
openDrawer('bill-drawer', { billId: from_transaction_id });
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="receipt-24" />}
|
||||
text={'Allocate landed cost'}
|
||||
onClick={handleAllocateCostClick}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
|
||||
<Card>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={transactions}
|
||||
ContextMenu={ActionsMenu}
|
||||
payload={{
|
||||
onDelete: handleDeleteTransaction,
|
||||
onFromTranscationClick: handleFromTransactionClick,
|
||||
}}
|
||||
className={'datatable--landed-cost-transactions'}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertsActions,
|
||||
withDialogActions,
|
||||
withDrawerActions,
|
||||
)(LocatedLandedCostTable);
|
||||
75
src/containers/Drawers/BillDrawer/components.js
Normal file
75
src/containers/Drawers/BillDrawer/components.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
|
||||
import { safeCallback } from 'utils';
|
||||
import { Icon } from 'components';
|
||||
/**
|
||||
* Actions menu.
|
||||
*/
|
||||
export function ActionsMenu({ row: { original }, payload: { onDelete } }) {
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
text={intl.get('delete_transaction')}
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDelete, original)}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* From transaction table cell.
|
||||
*/
|
||||
export function FromTransactionCell({
|
||||
row: { original },
|
||||
payload: { onFromTranscationClick }
|
||||
}) {
|
||||
// Handle the link click
|
||||
const handleAnchorClick = () => {
|
||||
onFromTranscationClick && onFromTranscationClick(original);
|
||||
};
|
||||
|
||||
return (
|
||||
<a href="#" onClick={handleAnchorClick}>
|
||||
{original.from_transaction_type} → {original.from_transaction_id}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve bill located landed cost table columns.
|
||||
*/
|
||||
export function useLocatedLandedCostColumns() {
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('name'),
|
||||
accessor: 'description',
|
||||
width: 150,
|
||||
className: 'name',
|
||||
},
|
||||
{
|
||||
Header: intl.get('amount'),
|
||||
accessor: 'formatted_amount',
|
||||
width: 100,
|
||||
className: 'amount',
|
||||
},
|
||||
{
|
||||
id: 'from_transaction',
|
||||
Header: intl.get('From transaction'),
|
||||
Cell: FromTransactionCell,
|
||||
width: 100,
|
||||
className: 'from-transaction',
|
||||
},
|
||||
{
|
||||
Header: intl.get('allocation_method'),
|
||||
accessor: 'allocation_method_formatted',
|
||||
width: 100,
|
||||
className: 'allocation-method',
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
}
|
||||
32
src/containers/Drawers/BillDrawer/index.js
Normal file
32
src/containers/Drawers/BillDrawer/index.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const BillDrawerContent = React.lazy(() => import('./BillDrawerContent'));
|
||||
|
||||
/**
|
||||
* Bill drawer.
|
||||
*/
|
||||
function BillDrawer({
|
||||
name,
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { billId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
name={name}
|
||||
style={{ minWidth: '700px', maxWidth: '900px' }}
|
||||
size={'65%'}
|
||||
>
|
||||
<DrawerSuspense>
|
||||
<BillDrawerContent billId={billId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDrawers())(BillDrawer);
|
||||
51
src/containers/Drawers/BillDrawer/utils.js
Normal file
51
src/containers/Drawers/BillDrawer/utils.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { FormatNumberCell } from '../../../components';
|
||||
|
||||
/**
|
||||
* Retrieve bill readonly details entries table columns.
|
||||
*/
|
||||
export const useBillReadonlyEntriesTableColumns = () =>
|
||||
React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('product_and_service'),
|
||||
accessor: 'item.name',
|
||||
width: 150,
|
||||
className: 'item',
|
||||
disableSortBy: true
|
||||
},
|
||||
{
|
||||
Header: intl.get('description'),
|
||||
accessor: 'description',
|
||||
className: 'description',
|
||||
disableSortBy: true
|
||||
},
|
||||
{
|
||||
Header: intl.get('quantity'),
|
||||
accessor: 'quantity',
|
||||
Cell: FormatNumberCell,
|
||||
width: 100,
|
||||
align: 'right',
|
||||
disableSortBy: true
|
||||
},
|
||||
{
|
||||
Header: intl.get('rate'),
|
||||
accessor: 'rate',
|
||||
Cell: FormatNumberCell,
|
||||
width: 100,
|
||||
align: 'right',
|
||||
disableSortBy: true
|
||||
},
|
||||
{
|
||||
Header: intl.get('amount'),
|
||||
accessor: 'amount',
|
||||
Cell: FormatNumberCell,
|
||||
width: 100,
|
||||
align: 'right',
|
||||
disableSortBy: true
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
18
src/containers/Drawers/ContactDetailDrawer/ContactDetail.js
Normal file
18
src/containers/Drawers/ContactDetailDrawer/ContactDetail.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import ContactDetailActionsBar from './ContactDetailActionsBar';
|
||||
import ContactDetailList from './ContactDetailList';
|
||||
import { Card } from 'components';
|
||||
|
||||
/**
|
||||
* contact detail.
|
||||
*/
|
||||
export default function ContactDetail() {
|
||||
return (
|
||||
<div className="view-detail-drawer">
|
||||
<ContactDetailActionsBar />
|
||||
<Card>
|
||||
<ContactDetailList />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
Button,
|
||||
NavbarGroup,
|
||||
Classes,
|
||||
NavbarDivider,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
|
||||
import { useContactDetailDrawerContext } from './ContactDetailDrawerProvider';
|
||||
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { Icon, FormattedMessage as T } from 'components';
|
||||
|
||||
import { safeCallback, compose } from 'utils';
|
||||
|
||||
function ContactDetailActionsBar({
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
const { contact, contactId } = useContactDetailDrawerContext();
|
||||
const history = useHistory();
|
||||
|
||||
// Handle edit contact.
|
||||
const onEditContact = () => {
|
||||
return contactId
|
||||
? (history.push(`/${contact?.contact_service}s/${contactId}/edit`),
|
||||
closeDrawer('contact-detail-drawer'))
|
||||
: null;
|
||||
};
|
||||
|
||||
// Handle delete contact.
|
||||
const onDeleteContact = () => {
|
||||
return contactId
|
||||
? (openAlert(`${contact?.contact_service}-delete`, { contactId }),
|
||||
closeDrawer('contact-detail-drawer'))
|
||||
: null;
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={intl.get('edit_contact', { name: contact?.contact_service })}
|
||||
onClick={safeCallback(onEditContact)}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDeleteContact)}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDrawerActions,
|
||||
withAlertsActions,
|
||||
)(ContactDetailActionsBar);
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
import ContactDetail from './ContactDetail';
|
||||
import { ContactDetailDrawerProvider } from './ContactDetailDrawerProvider';
|
||||
|
||||
import 'style/components/Drawers/ViewDetail/ViewDetail.scss';
|
||||
|
||||
/**
|
||||
* Contact detail drawer content.
|
||||
*/
|
||||
export default function ContactDetailDrawerContent({
|
||||
// #ownProp
|
||||
contact,
|
||||
}) {
|
||||
return (
|
||||
<ContactDetailDrawerProvider contactId={contact}>
|
||||
<ContactDetail />
|
||||
</ContactDetailDrawerProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { DrawerHeaderContent, DashboardInsider } from 'components';
|
||||
import { useContact } from 'hooks/query';
|
||||
|
||||
const ContactDetailDrawerContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Contact detail provider.
|
||||
*/
|
||||
function ContactDetailDrawerProvider({ contactId, ...props }) {
|
||||
// Handle fetch contact duplicate details.
|
||||
const { data: contact, isLoading: isContactLoading } = useContact(contactId, {
|
||||
enabled: !!contactId,
|
||||
});
|
||||
//provider.
|
||||
const provider = {
|
||||
contact,
|
||||
contactId,
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardInsider loading={isContactLoading}>
|
||||
<DrawerHeaderContent
|
||||
name="contact-detail-drawer"
|
||||
title={contact?.display_name}
|
||||
/>
|
||||
|
||||
<ContactDetailDrawerContext.Provider value={provider} {...props} />
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
const useContactDetailDrawerContext = () =>
|
||||
React.useContext(ContactDetailDrawerContext);
|
||||
|
||||
export { ContactDetailDrawerProvider, useContactDetailDrawerContext };
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Money } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
import { useContactDetailDrawerContext } from './ContactDetailDrawerProvider';
|
||||
import { DetailItem } from '../../../components/Details';
|
||||
|
||||
export default function ContactDetailList({}) {
|
||||
const { contact } = useContactDetailDrawerContext();
|
||||
|
||||
return (
|
||||
<div className="details-menu">
|
||||
<div className="details-menu--vertical">
|
||||
<DetailItem
|
||||
label={intl.get('display_name')}
|
||||
children={contact.display_name}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('balance')}
|
||||
children={
|
||||
<Money
|
||||
amount={contact?.balance}
|
||||
currency={contact?.currency_code}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="details-menu--horizontal">
|
||||
<DetailItem
|
||||
label={intl.get('closing_balance')}
|
||||
children={
|
||||
<Money
|
||||
amount={contact.closing_balance}
|
||||
currency={contact?.currency_code}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('contact_type')}
|
||||
children={contact.contact_type}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('email')}
|
||||
children={contact.email ? contact.email : '--'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
30
src/containers/Drawers/ContactDetailDrawer/index.js
Normal file
30
src/containers/Drawers/ContactDetailDrawer/index.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const ContactDetailDrawerContent = React.lazy(() =>
|
||||
import('./ContactDetailDrawerContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Contact detail drawer.
|
||||
*/
|
||||
function ContactDetailDrawer({
|
||||
name,
|
||||
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { contactId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer isOpen={isOpen} name={name} size={'750px'}>
|
||||
<DrawerSuspense>
|
||||
<ContactDetailDrawerContent contact={contactId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDrawers())(ContactDetailDrawer);
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { Card } from 'components';
|
||||
|
||||
import CustomerDetailsActionsBar from './CustomerDetailsActionsBar';
|
||||
import CustomerDetailsHeader from './CustomerDetailsHeader';
|
||||
|
||||
import Style from './CustomerDetailsDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* contact detail.
|
||||
*/
|
||||
export default function CustomerDetails() {
|
||||
return (
|
||||
<div className={clsx(Style.root)}>
|
||||
<CustomerDetailsActionsBar />
|
||||
|
||||
<Card>
|
||||
<CustomerDetailsHeader />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
Button,
|
||||
NavbarGroup,
|
||||
Classes,
|
||||
NavbarDivider,
|
||||
Intent,
|
||||
Position,
|
||||
PopoverInteractionKind,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import clsx from 'classnames';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
|
||||
import { useCustomerDetailsDrawerContext } from './CustomerDetailsDrawerProvider';
|
||||
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { Icon, FormattedMessage as T } from 'components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Customer details actions bar.
|
||||
*/
|
||||
function CustomerDetailsActionsBar({
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
const { contact, customerId } = useCustomerDetailsDrawerContext();
|
||||
const history = useHistory();
|
||||
|
||||
// Handle new invoice button click.
|
||||
const handleNewInvoiceClick = () => {
|
||||
history.push('invoices/new');
|
||||
closeDrawer('customer-details-drawer');
|
||||
};
|
||||
// Handle new receipt button click.
|
||||
const handleNewReceiptClick = () => {
|
||||
history.push('receipts/new');
|
||||
closeDrawer('customer-details-drawer');
|
||||
};
|
||||
// Handle new payment receive button click.
|
||||
const handleNewPaymentClick = () => {
|
||||
history.push('payment-receives/new');
|
||||
closeDrawer('customer-details-drawer');
|
||||
};
|
||||
// Handle new estimate button click.
|
||||
const handleNewEstimateClick = () => {
|
||||
history.push('estimates/new');
|
||||
closeDrawer('customer-details-drawer');
|
||||
};
|
||||
|
||||
const handleDeleteCustomer = () => {
|
||||
openAlert(`customer-delete`, { contactId: customerId });
|
||||
closeDrawer('customer-details-drawer');
|
||||
};
|
||||
const handleEditContact = () => {
|
||||
history.push(`/customers/${customerId}/edit`);
|
||||
closeDrawer('customer-details-drawer');
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Popover
|
||||
content={
|
||||
<Menu>
|
||||
<MenuItem
|
||||
text={<T id={'customer.drawer.action.new_invoice'} />}
|
||||
onClick={handleNewInvoiceClick}
|
||||
/>
|
||||
<MenuItem
|
||||
text={<T id={'customer.drawer.action.new_estimate'} />}
|
||||
onClick={handleNewEstimateClick}
|
||||
/>
|
||||
<MenuItem
|
||||
text={<T id={'customer.drawer.action.new_receipt'} />}
|
||||
onClick={handleNewReceiptClick}
|
||||
/>
|
||||
<MenuItem
|
||||
text={<T id={'customer.drawer.action.new_payment'} />}
|
||||
onClick={handleNewPaymentClick}
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
minimal={true}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
>
|
||||
<Button
|
||||
className={clsx(Classes.MINIMAL)}
|
||||
text={<T id={'customer.drawer.action.new_transaction'} />}
|
||||
icon={<Icon icon={'plus'} />}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
<NavbarDivider />
|
||||
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={intl.get('edit_contact', { name: contact?.contact_service })}
|
||||
onClick={handleEditContact}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteCustomer}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDrawerActions,
|
||||
withAlertsActions,
|
||||
)(CustomerDetailsActionsBar);
|
||||
@@ -0,0 +1,15 @@
|
||||
.root {
|
||||
|
||||
&_content {
|
||||
|
||||
&_primary {
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
}
|
||||
}
|
||||
|
||||
:global .card {
|
||||
margin: 15px;
|
||||
padding: 18px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
import { DrawerBody } from 'components';
|
||||
import CustomerDetails from './CustomerDetails';
|
||||
import { CustomerDetailsDrawerProvider } from './CustomerDetailsDrawerProvider';
|
||||
|
||||
/**
|
||||
* Contact detail drawer content.
|
||||
*/
|
||||
export default function CustomerDetailsDrawerContent({
|
||||
// #ownProp
|
||||
customerId,
|
||||
}) {
|
||||
return (
|
||||
<CustomerDetailsDrawerProvider customerId={customerId}>
|
||||
<DrawerBody>
|
||||
<CustomerDetails />
|
||||
</DrawerBody>
|
||||
</CustomerDetailsDrawerProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { DrawerHeaderContent, DrawerLoading } from 'components';
|
||||
import { useCustomer } from 'hooks/query';
|
||||
|
||||
const ContactDetailDrawerContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Contact detail provider.
|
||||
*/
|
||||
function CustomerDetailsDrawerProvider({ customerId, ...props }) {
|
||||
// Handle fetch customer details.
|
||||
const { data: customer, isLoading: isCustomerLoading } = useCustomer(
|
||||
customerId,
|
||||
{
|
||||
enabled: !!customerId,
|
||||
},
|
||||
);
|
||||
// Provider.
|
||||
const provider = {
|
||||
customer,
|
||||
customerId,
|
||||
isCustomerLoading,
|
||||
};
|
||||
|
||||
return (
|
||||
<DrawerLoading loading={isCustomerLoading}>
|
||||
<DrawerHeaderContent
|
||||
name="customer-details-drawer"
|
||||
title={customer?.display_name}
|
||||
/>
|
||||
<ContactDetailDrawerContext.Provider value={provider} {...props} />
|
||||
</DrawerLoading>
|
||||
);
|
||||
}
|
||||
|
||||
const useCustomerDetailsDrawerContext = () =>
|
||||
React.useContext(ContactDetailDrawerContext);
|
||||
|
||||
export { CustomerDetailsDrawerProvider, useCustomerDetailsDrawerContext };
|
||||
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import clsx from 'classnames';
|
||||
import { defaultTo } from 'lodash';
|
||||
|
||||
import { DetailsMenu, DetailItem, T } from 'components';
|
||||
|
||||
import { useCustomerDetailsDrawerContext } from './CustomerDetailsDrawerProvider';
|
||||
|
||||
import Style from './CustomerDetailsDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Customer details header.
|
||||
*/
|
||||
export default function CustomerDetailsHeader() {
|
||||
const { customer } = useCustomerDetailsDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(Style.root_content)}>
|
||||
<DetailsMenu
|
||||
direction={'vertical'}
|
||||
className={clsx(Style.root_content_primary)}
|
||||
>
|
||||
<DetailItem
|
||||
name={'outstanding-receivable'}
|
||||
label={<T id={'customer.drawer.label.outstanding_receivable'} />}
|
||||
>
|
||||
<h3 class="big-number">{customer.formatted_balance}</h3>
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem
|
||||
label={<T id={'customer.drawer.label.customer_name'} />}
|
||||
name={'name'}
|
||||
children={customer?.display_name}
|
||||
/>
|
||||
<DetailItem
|
||||
label={<T id={'customer.drawer.label.customer_type'} />}
|
||||
name={'type'}
|
||||
children={customer?.customer_type}
|
||||
/>
|
||||
<DetailItem label={<T id={'customer.drawer.label.unused_credits'} />}>
|
||||
0
|
||||
</DetailItem>
|
||||
</DetailsMenu>
|
||||
|
||||
<DetailsMenu direction={'horizantal'} minLabelSize={'175px'}>
|
||||
<DetailItem
|
||||
label={<T id={'customer.drawer.label.company_name'} />}
|
||||
children={defaultTo(customer?.company_name, '--')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={<T id={'customer.drawer.label.email'} />}
|
||||
children={defaultTo(customer?.email, '--')}
|
||||
/>
|
||||
<DetailItem label={<T id={'customer.drawer.label.phone_number'} />}>
|
||||
<div>{customer?.personal_phone} </div>
|
||||
<div>{customer?.work_phone} </div>
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem
|
||||
label={<T id={'customer.drawer.label.website'} />}
|
||||
children={defaultTo(customer?.website, '--')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={<T id={'customer.drawer.label.opening_balance'} />}
|
||||
children={customer?.formatted_opening_balance}
|
||||
/>
|
||||
<DetailItem
|
||||
label={<T id={'customer.drawer.label.opening_balance_at'} />}
|
||||
children={customer?.formatted_opening_balance_at}
|
||||
/>
|
||||
<DetailItem
|
||||
label={<T id={'customer.drawer.label.currency'} />}
|
||||
children={customer?.currency_code}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
30
src/containers/Drawers/CustomerDetailsDrawer/index.js
Normal file
30
src/containers/Drawers/CustomerDetailsDrawer/index.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const CustomerDetailsDrawerContent = React.lazy(() =>
|
||||
import('./CustomerDetailsDrawerContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Contact detail drawer.
|
||||
*/
|
||||
function CustomerDetailsDrawer({
|
||||
name,
|
||||
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { customerId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer isOpen={isOpen} name={name} size={'750px'}>
|
||||
<DrawerSuspense>
|
||||
<CustomerDetailsDrawerContent customerId={customerId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDrawers())(CustomerDetailsDrawer);
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Tab } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DrawerMainTabs } from 'components';
|
||||
|
||||
import EstimateDetailPanel from './EstimateDetailPanel';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
|
||||
|
||||
/**
|
||||
* Estimate view detail
|
||||
*/
|
||||
export default function EstimateDetail() {
|
||||
return (
|
||||
<div className={clsx(EstimateDetailsCls.root)}>
|
||||
<DrawerMainTabs>
|
||||
<Tab
|
||||
title={intl.get('details')}
|
||||
id={'details'}
|
||||
panel={<EstimateDetailPanel />}
|
||||
/>
|
||||
</DrawerMainTabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Button,
|
||||
NavbarGroup,
|
||||
Classes,
|
||||
NavbarDivider,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { Icon, FormattedMessage as T } from 'components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Estimate read-only details actions bar of the drawer.
|
||||
*/
|
||||
function EstimateDetailActionsBar({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
const { estimateId } = useEstimateDetailDrawerContext();
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
// Handle edit sale estimate.
|
||||
const handleEditEstimate = () => {
|
||||
history.push(`/estimates/${estimateId}/edit`);
|
||||
closeDrawer('estimate-detail-drawer');
|
||||
};
|
||||
|
||||
// Handle delete sale estimate.
|
||||
const handleDeleteEstimate = () => {
|
||||
openAlert('estimate-delete', { estimateId });
|
||||
};
|
||||
|
||||
// Handle print estimate.
|
||||
const handlePrintEstimate = () => {
|
||||
openDialog('estimate-pdf-preview', { estimateId });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={<T id={'edit_estimate'} />}
|
||||
onClick={handleEditEstimate}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="print-16" />}
|
||||
text={<T id={'print'} />}
|
||||
onClick={handlePrintEstimate}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteEstimate}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
withAlertsActions,
|
||||
withDrawerActions,
|
||||
)(EstimateDetailActionsBar);
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { DrawerBody } from 'components';
|
||||
|
||||
import EstimateDetail from './EstimateDetail';
|
||||
import { EstimateDetailDrawerProvider } from './EstimateDetailDrawerProvider';
|
||||
|
||||
/**
|
||||
* Estimate detail drawer content.
|
||||
*/
|
||||
export default function EstimateDetailDrawerContent({
|
||||
// #ownProp
|
||||
estimateId,
|
||||
}) {
|
||||
return (
|
||||
<EstimateDetailDrawerProvider estimateId={estimateId}>
|
||||
<DrawerBody>
|
||||
<EstimateDetail />
|
||||
</DrawerBody>
|
||||
</EstimateDetailDrawerProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { useEstimate } from 'hooks/query';
|
||||
import { DrawerHeaderContent, DrawerLoading } from 'components';
|
||||
|
||||
const EstimateDetailDrawerContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Estimate detail provider.
|
||||
*/
|
||||
function EstimateDetailDrawerProvider({ estimateId, ...props }) {
|
||||
// Fetches the estimate by the given id.
|
||||
const { data: estimate, isLoading: isEstimateLoading } = useEstimate(
|
||||
estimateId,
|
||||
{ enabled: !!estimateId },
|
||||
);
|
||||
|
||||
const provider = {
|
||||
estimateId,
|
||||
estimate,
|
||||
};
|
||||
|
||||
return (
|
||||
<DrawerLoading loading={isEstimateLoading}>
|
||||
<DrawerHeaderContent
|
||||
name="estimate-detail-drawer"
|
||||
title={intl.get('estimate_details')}
|
||||
/>
|
||||
<EstimateDetailDrawerContext.Provider value={provider} {...props} />
|
||||
</DrawerLoading>
|
||||
);
|
||||
}
|
||||
|
||||
const useEstimateDetailDrawerContext = () =>
|
||||
React.useContext(EstimateDetailDrawerContext);
|
||||
|
||||
export { EstimateDetailDrawerProvider, useEstimateDetailDrawerContext };
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { T, TotalLines, TotalLine, If } from 'components';
|
||||
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
|
||||
import { FormatNumber } from '../../../components';
|
||||
|
||||
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
|
||||
|
||||
/**
|
||||
* Estimate details panel footer content.
|
||||
*/
|
||||
export default function EstimateDetailFooter() {
|
||||
const { estimate } = useEstimateDetailDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(EstimateDetailsCls.detail_panel_footer)}>
|
||||
<TotalLines className={clsx(EstimateDetailsCls.total_lines)}>
|
||||
<TotalLine
|
||||
title={<T id={'estimate.details.subtotal'} />}
|
||||
value={<FormatNumber value={estimate.amount} />}
|
||||
className={EstimateDetailsCls.total_line_subtotal}
|
||||
/>
|
||||
<TotalLine
|
||||
title={<T id={'estimate.details.total'} />}
|
||||
value={estimate.formatted_amount}
|
||||
className={EstimateDetailsCls.total_line_total}
|
||||
/>
|
||||
</TotalLines>
|
||||
|
||||
<If condition={false}>
|
||||
<div className={clsx(EstimateDetailsCls.detail_panel_note)}>
|
||||
<p>
|
||||
<b><T id={'estimate.details.note'} />:</b> --
|
||||
</p>
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { defaultTo } from 'lodash';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { FormatDate, T, DetailsMenu, DetailItem } from 'components';
|
||||
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
|
||||
|
||||
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
|
||||
|
||||
/**
|
||||
* Estimate read-only details drawer header.
|
||||
*/
|
||||
export default function EstimateDetailHeader() {
|
||||
const { estimate } = useEstimateDetailDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(EstimateDetailsCls.detail_panel_header)}>
|
||||
<DetailsMenu>
|
||||
<DetailItem label={intl.get('amount')}>
|
||||
<span class="big-number">{estimate.formatted_amount}</span>
|
||||
</DetailItem>
|
||||
<DetailItem
|
||||
label={intl.get('estimate.details.estimate_number')}
|
||||
children={defaultTo(estimate.estimate_number, '-')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('customer_name')}
|
||||
children={estimate.customer?.display_name}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('estimate_date')}
|
||||
children={estimate.formatted_estimate_date}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('expiration_date')}
|
||||
children={estimate.formatted_expiration_date}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
|
||||
<DetailsMenu direction={'horizantal'} minLabelSize={'140px'}>
|
||||
<DetailItem
|
||||
label={intl.get('reference')}
|
||||
children={defaultTo(estimate.reference, '-')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={<T id={'estimate.details.created_at'} />}
|
||||
children={<FormatDate value={estimate.created_at} />}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { Card } from 'components';
|
||||
|
||||
import EstimateDetailActionsBar from './EstimateDetailActionsBar';
|
||||
import EstimateDetailHeader from './EstimateDetailHeader';
|
||||
import EstimateDetailTable from './EstimateDetailTable';
|
||||
|
||||
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
|
||||
import EstimateDetailFooter from './EstimateDetailFooter';
|
||||
|
||||
export default function EstimateDetailTab() {
|
||||
return (
|
||||
<div className={clsx(EstimateDetailsCls.detail_panel)}>
|
||||
<EstimateDetailActionsBar />
|
||||
|
||||
<Card>
|
||||
<EstimateDetailHeader />
|
||||
<EstimateDetailTable />
|
||||
<EstimateDetailFooter />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { DataTable } from 'components';
|
||||
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
|
||||
|
||||
import { useEstimateReadonlyEntriesColumns } from './utils';
|
||||
|
||||
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
|
||||
|
||||
/**
|
||||
* Estimate detail table.
|
||||
*/
|
||||
export default function EstimateDetailTable() {
|
||||
const {
|
||||
estimate: { entries },
|
||||
} = useEstimateDetailDrawerContext();
|
||||
|
||||
// Estimate entries table columns.
|
||||
const columns = useEstimateReadonlyEntriesColumns();
|
||||
|
||||
return (
|
||||
<div className={clsx(EstimateDetailsCls.detail_panel_table)}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={entries}
|
||||
className={'table-constrant'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
const globalStateClassesMapping = {
|
||||
active: 'active',
|
||||
checked: 'checked',
|
||||
completed: 'completed',
|
||||
disabled: 'disabled',
|
||||
error: 'error',
|
||||
expanded: 'expanded',
|
||||
focused: 'focused',
|
||||
focusVisible: 'focusVisible',
|
||||
required: 'required',
|
||||
selected: 'selected',
|
||||
};
|
||||
|
||||
function generateUtilityClass(componentName, slot) {
|
||||
const globalStateClass = globalStateClassesMapping[slot];
|
||||
return globalStateClass || `${componentName}__${slot}`;
|
||||
}
|
||||
|
||||
function generateUtilityClasses(componentName, modifiers) {
|
||||
const result = {
|
||||
root: componentName,
|
||||
};
|
||||
modifiers.forEach((modifier) => {
|
||||
result[modifier] = generateUtilityClass(componentName, modifier);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export const EstimateDrawerCls = generateUtilityClasses('estimate-drawer', [
|
||||
'content',
|
||||
]);
|
||||
31
src/containers/Drawers/EstimateDetailDrawer/index.js
Normal file
31
src/containers/Drawers/EstimateDetailDrawer/index.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const EstimateDetailDrawerContent = React.lazy(() =>
|
||||
import('./EstimateDetailDrawerContent'),
|
||||
);
|
||||
|
||||
function EstimateDetailDrawer({
|
||||
name,
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { estimateId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
name={name}
|
||||
style={{ minWidth: '700px', maxWidth: '900px' }}
|
||||
size={'65%'}
|
||||
>
|
||||
<DrawerSuspense>
|
||||
<EstimateDetailDrawerContent estimateId={estimateId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDrawers())(EstimateDetailDrawer);
|
||||
47
src/containers/Drawers/EstimateDetailDrawer/utils.js
Normal file
47
src/containers/Drawers/EstimateDetailDrawer/utils.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { FormatNumberCell } from '../../../components';
|
||||
|
||||
/**
|
||||
* Retrieve table columns of estimate readonly entries details.
|
||||
*/
|
||||
export const useEstimateReadonlyEntriesColumns = () =>
|
||||
React.useMemo(() => [
|
||||
{
|
||||
Header: intl.get('product_and_service'),
|
||||
accessor: 'item.name',
|
||||
width: 150,
|
||||
className: 'name',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('description'),
|
||||
accessor: 'description',
|
||||
className: 'description',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('quantity'),
|
||||
accessor: 'quantity',
|
||||
Cell: FormatNumberCell,
|
||||
width: 100,
|
||||
align: 'right',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('rate'),
|
||||
accessor: 'rate',
|
||||
Cell: FormatNumberCell,
|
||||
width: 100,
|
||||
align: 'right',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('amount'),
|
||||
accessor: 'amount',
|
||||
Cell: FormatNumberCell,
|
||||
width: 100,
|
||||
align: 'right',
|
||||
disableSortBy: true,
|
||||
},
|
||||
], []);
|
||||
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import Icon from 'components/Icon';
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
NavbarGroup,
|
||||
Intent,
|
||||
NavbarDivider,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
|
||||
|
||||
/**
|
||||
* Expense drawer action bar.
|
||||
*/
|
||||
function ExpenseDrawerActionBar({
|
||||
// #withAlertsDialog
|
||||
openAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { expense } = useExpenseDrawerContext();
|
||||
|
||||
// Handle the expense edit action.
|
||||
const handleEditExpense = () => {
|
||||
history.push(`/expenses/${expense.id}/edit`);
|
||||
closeDrawer('expense-drawer');
|
||||
};
|
||||
|
||||
// Handle the expense delete action.
|
||||
const handleDeleteExpense = () => {
|
||||
openAlert('expense-delete', { expenseId: expense.id });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={<T id={'edit_expense'} />}
|
||||
onClick={handleEditExpense}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteExpense}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertsActions,
|
||||
withDrawerActions,
|
||||
)(ExpenseDrawerActionBar);
|
||||
24
src/containers/Drawers/ExpenseDrawer/ExpenseDrawerContent.js
Normal file
24
src/containers/Drawers/ExpenseDrawer/ExpenseDrawerContent.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
import { DrawerBody } from 'components';
|
||||
|
||||
import 'style/components/Drawers/ExpenseDrawer.scss';
|
||||
|
||||
import { ExpenseDrawerProvider } from './ExpenseDrawerProvider';
|
||||
import ExpenseDrawerDetails from './ExpenseDrawerDetails';
|
||||
|
||||
/**
|
||||
* Expense drawer content.
|
||||
*/
|
||||
export default function ExpenseDrawerContent({
|
||||
// #ownProp
|
||||
expenseId,
|
||||
}) {
|
||||
return (
|
||||
<ExpenseDrawerProvider expenseId={expenseId}>
|
||||
<DrawerBody>
|
||||
<ExpenseDrawerDetails />
|
||||
</DrawerBody>
|
||||
</ExpenseDrawerProvider>
|
||||
);
|
||||
}
|
||||
27
src/containers/Drawers/ExpenseDrawer/ExpenseDrawerDetails.js
Normal file
27
src/containers/Drawers/ExpenseDrawer/ExpenseDrawerDetails.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Card } from 'components';
|
||||
|
||||
import ExpenseDrawerActionBar from './ExpenseDrawerActionBar';
|
||||
import ExpenseDrawerHeader from './ExpenseDrawerHeader';
|
||||
import ExpenseDrawerTable from './ExpenseDrawerTable';
|
||||
import ExpenseDrawerFooter from './ExpenseDrawerFooter';
|
||||
|
||||
/**
|
||||
* Expense view details.
|
||||
*/
|
||||
export default function ExpenseDrawerDetails() {
|
||||
return (
|
||||
<div className={'expense-drawer'}>
|
||||
<ExpenseDrawerActionBar />
|
||||
|
||||
<div className="expense-drawer__content">
|
||||
<Card>
|
||||
<ExpenseDrawerHeader />
|
||||
<ExpenseDrawerTable />
|
||||
<ExpenseDrawerFooter />
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
33
src/containers/Drawers/ExpenseDrawer/ExpenseDrawerFooter.js
Normal file
33
src/containers/Drawers/ExpenseDrawer/ExpenseDrawerFooter.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { T } from 'components';
|
||||
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
|
||||
|
||||
import { FormatNumber } from '../../../components';
|
||||
|
||||
/**
|
||||
* Footer details of expense readonly details.
|
||||
*/
|
||||
export default function ExpenseDrawerFooter() {
|
||||
const { expense } = useExpenseDrawerContext();
|
||||
|
||||
return (
|
||||
<div className="expense-drawer__content-footer">
|
||||
<div class="total-lines">
|
||||
<div class="total-lines__line total-lines__line--subtotal">
|
||||
<div class="title">
|
||||
<T id={'expense.details.subtotal'} />
|
||||
</div>
|
||||
<div class="amount">
|
||||
{<FormatNumber value={expense.total_amount} />}
|
||||
</div>
|
||||
</div>
|
||||
<div class="total-lines__line total-lines__line--total">
|
||||
<div class="title">
|
||||
<T id={'expense.details.total'} />
|
||||
</div>
|
||||
<div class="amount">{expense.formatted_amount}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
58
src/containers/Drawers/ExpenseDrawer/ExpenseDrawerHeader.js
Normal file
58
src/containers/Drawers/ExpenseDrawer/ExpenseDrawerHeader.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { defaultTo } from 'lodash';
|
||||
|
||||
import { DetailItem, DetailsMenu, Money } from 'components';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
|
||||
|
||||
/**
|
||||
* Expense drawer content.
|
||||
*/
|
||||
export default function ExpenseDrawerHeader() {
|
||||
const {
|
||||
expense: {
|
||||
total_amount,
|
||||
payment_date,
|
||||
currency_code,
|
||||
reference_no,
|
||||
description,
|
||||
published_at,
|
||||
},
|
||||
} = useExpenseDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={'expense-drawer__content-header'}>
|
||||
<DetailsMenu>
|
||||
<DetailItem name={'amount'} label={<T id={'full_amount'} />}>
|
||||
<h3 class="big-number">
|
||||
<Money amount={total_amount} currency={currency_code} />
|
||||
</h3>
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'date'} label={<T id={'date'} />}>
|
||||
{moment(payment_date).format('YYYY MMM DD')}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'currency'} label={<T id={'currency'} />}>
|
||||
{currency_code}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'reference'} label={<T id={'reference_no'} />}>
|
||||
{defaultTo(reference_no, '-')}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem label={<T id={'published_at'} />}>
|
||||
{moment(published_at).format('YYYY MMM DD')}
|
||||
</DetailItem>
|
||||
</DetailsMenu>
|
||||
|
||||
<DetailsMenu direction={'horizantal'}>
|
||||
<DetailItem label={<T id={'description'} />}>
|
||||
{defaultTo(description, '—')}
|
||||
</DetailItem>
|
||||
<DetailItem label={<T id={'created_at'} />}>2021 Aug 24</DetailItem>
|
||||
</DetailsMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { useExpense } from 'hooks/query';
|
||||
import { DrawerLoading } from 'components';
|
||||
|
||||
const ExpenseDrawerDrawerContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Expense drawer provider.
|
||||
*/
|
||||
function ExpenseDrawerProvider({ expenseId, ...props }) {
|
||||
// Fetch the expense details.
|
||||
const {
|
||||
data: expense,
|
||||
isLoading: isExpenseLoading,
|
||||
isFetching: isExpenseFetching,
|
||||
} = useExpense(expenseId, {
|
||||
enabled: !!expenseId,
|
||||
});
|
||||
|
||||
// Provider.
|
||||
const provider = {
|
||||
expenseId,
|
||||
expense,
|
||||
|
||||
isExpenseFetching,
|
||||
isExpenseLoading,
|
||||
};
|
||||
|
||||
return (
|
||||
<DrawerLoading loading={isExpenseLoading}>
|
||||
<ExpenseDrawerDrawerContext.Provider value={provider} {...props} />
|
||||
</DrawerLoading>
|
||||
);
|
||||
}
|
||||
const useExpenseDrawerContext = () =>
|
||||
React.useContext(ExpenseDrawerDrawerContext);
|
||||
|
||||
export { ExpenseDrawerProvider, useExpenseDrawerContext };
|
||||
18
src/containers/Drawers/ExpenseDrawer/ExpenseDrawerTable.js
Normal file
18
src/containers/Drawers/ExpenseDrawer/ExpenseDrawerTable.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { DataTable } from 'components';
|
||||
import { useExpenseReadEntriesColumns } from './utils';
|
||||
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
|
||||
|
||||
/**
|
||||
* Expense details table.
|
||||
*/
|
||||
export default function ExpenseDrawerTable() {
|
||||
const columns = useExpenseReadEntriesColumns();
|
||||
const { expense } = useExpenseDrawerContext();
|
||||
|
||||
return (
|
||||
<div className="expense-drawer__content--table">
|
||||
<DataTable columns={columns} data={expense.categories} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
src/containers/Drawers/ExpenseDrawer/index.js
Normal file
36
src/containers/Drawers/ExpenseDrawer/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React, { lazy } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const ExpenseDrawerContent = lazy(() => import('./ExpenseDrawerContent'));
|
||||
|
||||
/**
|
||||
* Expense drawer.
|
||||
*/
|
||||
function ExpenseDrawer({
|
||||
name,
|
||||
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { expenseId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
name={name}
|
||||
title={intl.get('expense')}
|
||||
size={'65%'}
|
||||
style={{ minWidth: '700px', maxWidth: '900px' }}
|
||||
>
|
||||
<DrawerSuspense>
|
||||
<ExpenseDrawerContent expenseId={expenseId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDrawers())(ExpenseDrawer);
|
||||
37
src/containers/Drawers/ExpenseDrawer/utils.js
Normal file
37
src/containers/Drawers/ExpenseDrawer/utils.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { FormatNumberCell } from '../../../components';
|
||||
|
||||
/**
|
||||
* Retrieve expense readonly details entries table columns.
|
||||
*/
|
||||
export const useExpenseReadEntriesColumns = () =>
|
||||
React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('expense_account'),
|
||||
accessor: 'expense_account.name',
|
||||
width: 110,
|
||||
disableSortBy: true,
|
||||
className: 'account',
|
||||
},
|
||||
{
|
||||
Header: intl.get('description'),
|
||||
accessor: 'description',
|
||||
width: 110,
|
||||
disableSortBy: true,
|
||||
className: 'description',
|
||||
},
|
||||
{
|
||||
Header: intl.get('amount'),
|
||||
accessor: 'amount',
|
||||
Cell: FormatNumberCell,
|
||||
width: 100,
|
||||
disableSortBy: true,
|
||||
className: 'amount',
|
||||
align: 'right',
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { Card } from 'components';
|
||||
|
||||
import InventoryAdjustmentDetailActionsBar from './InventoryAdjustmentDetailActionsBar';
|
||||
import InventoryAdjustmentDetailHeader from './InventoryAdjustmentDetailHeader';
|
||||
import InventoryAdjustmentDetailTable from './InventoryAdjustmentDetailTable';
|
||||
|
||||
import InventoryAdjustmentDrawerCls from 'style/components/Drawers/InventoryAdjustmentDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Inventory adjustment detail
|
||||
*/
|
||||
export default function InventoryAdjustmentDetail() {
|
||||
return (
|
||||
<div className={clsx(InventoryAdjustmentDrawerCls.detail_panel)}>
|
||||
<InventoryAdjustmentDetailActionsBar />
|
||||
|
||||
<Card>
|
||||
<InventoryAdjustmentDetailHeader />
|
||||
<InventoryAdjustmentDetailTable />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Button, NavbarGroup, Classes, Intent } from '@blueprintjs/core';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
|
||||
import { useInventoryAdjustmentDrawerContext } from './InventoryAdjustmentDrawerProvider';
|
||||
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
|
||||
import { Icon, FormattedMessage as T } from 'components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Inventory adjustment detail actions bar.
|
||||
*/
|
||||
function InventoryAdjustmentDetailActionsBar({
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
}) {
|
||||
const { inventoryId } = useInventoryAdjustmentDrawerContext();
|
||||
|
||||
// Handle delete inventory adjustment.
|
||||
const handleDeleteInventoryAdjustment = () => {
|
||||
openAlert('inventory-adjustment-delete', { inventoryId });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteInventoryAdjustment}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withAlertsActions)(InventoryAdjustmentDetailActionsBar);
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
import intl from 'react-intl-universal';
|
||||
import { defaultTo } from 'lodash';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { DetailsMenu, DetailItem } from 'components';
|
||||
import { useInventoryAdjustmentDrawerContext } from './InventoryAdjustmentDrawerProvider';
|
||||
|
||||
import InventoryAdjustmentDrawerCls from 'style/components/Drawers/InventoryAdjustmentDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Inventory detail header.
|
||||
*/
|
||||
export default function InventoryAdjustmentDetailHeader() {
|
||||
const {
|
||||
inventoryAdjustment: {
|
||||
date,
|
||||
type,
|
||||
adjustment_account,
|
||||
inventory_direction,
|
||||
description,
|
||||
reference_no,
|
||||
reason,
|
||||
published_at,
|
||||
created_at,
|
||||
},
|
||||
} = useInventoryAdjustmentDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(InventoryAdjustmentDrawerCls.detail_panel_header)}>
|
||||
<DetailsMenu>
|
||||
<DetailItem label={intl.get('date')}>
|
||||
{moment(date).format('YYYY MMM DD')}
|
||||
</DetailItem>
|
||||
<DetailItem label={intl.get('type')}>{type}</DetailItem>
|
||||
<DetailItem label={intl.get('adjustment_account')}>
|
||||
{adjustment_account.name}
|
||||
</DetailItem>
|
||||
<DetailItem name={'reference'} label={intl.get('reference_no')}>
|
||||
{defaultTo(reference_no, '-')}
|
||||
</DetailItem>
|
||||
<DetailItem label={intl.get('published_at')}>
|
||||
{moment(published_at).format('YYYY MMM DD')}
|
||||
</DetailItem>
|
||||
</DetailsMenu>
|
||||
<DetailsMenu direction={'horizantal'}>
|
||||
<DetailItem label={intl.get('reason')}>
|
||||
{defaultTo(reason, '—')}
|
||||
</DetailItem>
|
||||
<DetailItem label={intl.get('created_at')}>
|
||||
{moment(created_at).format('YYYY MMM DD')}
|
||||
</DetailItem>
|
||||
</DetailsMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { DataTable } from 'components';
|
||||
|
||||
import { useInventoryAdjustmentEntriesColumns } from './utils';
|
||||
import { useInventoryAdjustmentDrawerContext } from './InventoryAdjustmentDrawerProvider';
|
||||
|
||||
import InventoryAdjustmentDrawerCls from 'style/components/Drawers/InventoryAdjustmentDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Inventory adjustment detail entries table.
|
||||
*/
|
||||
export default function InventoryAdjustmentDetailTable() {
|
||||
const columns = useInventoryAdjustmentEntriesColumns();
|
||||
|
||||
const {
|
||||
inventoryAdjustment: { entries },
|
||||
} = useInventoryAdjustmentDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(InventoryAdjustmentDrawerCls.detail_panel_table)}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={entries}
|
||||
className={'table-constrant'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { DrawerBody } from 'components';
|
||||
|
||||
import InventoryAdjustmentDetail from './InventoryAdjustmentDetail';
|
||||
import { InventoryAdjustmentDrawerProvider } from './InventoryAdjustmentDrawerProvider';
|
||||
|
||||
/**
|
||||
* Inventory adjustment drawer content.
|
||||
*/
|
||||
export default function InventoryAdjustmentDrawerContent({ inventoryId }) {
|
||||
return (
|
||||
<InventoryAdjustmentDrawerProvider inventoryId={inventoryId}>
|
||||
<DrawerBody>
|
||||
<InventoryAdjustmentDetail />
|
||||
</DrawerBody>
|
||||
</InventoryAdjustmentDrawerProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DrawerHeaderContent, DrawerLoading } from 'components';
|
||||
import { useInventoryAdjustment } from 'hooks/query';
|
||||
|
||||
const InventoryAdjustmentDrawerContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Inventory adjustment drawer provider.
|
||||
*/
|
||||
function InventoryAdjustmentDrawerProvider({ inventoryId, ...props }) {
|
||||
// Handle fetch inventory adjustment .
|
||||
const { data: inventoryAdjustment, isLoading: isAdjustmentsLoading } =
|
||||
useInventoryAdjustment(inventoryId, {
|
||||
enabled: !!inventoryId,
|
||||
});
|
||||
|
||||
//provider.
|
||||
const provider = {
|
||||
inventoryAdjustment,
|
||||
inventoryId,
|
||||
};
|
||||
|
||||
return (
|
||||
<DrawerLoading loading={isAdjustmentsLoading}>
|
||||
<DrawerHeaderContent
|
||||
name="inventory-adjustment-drawer"
|
||||
title={intl.get('inventory_adjustment.details_drawer.title')}
|
||||
/>
|
||||
<InventoryAdjustmentDrawerContext.Provider value={provider} {...props} />
|
||||
</DrawerLoading>
|
||||
);
|
||||
}
|
||||
|
||||
const useInventoryAdjustmentDrawerContext = () =>
|
||||
React.useContext(InventoryAdjustmentDrawerContext);
|
||||
|
||||
export {
|
||||
InventoryAdjustmentDrawerProvider,
|
||||
useInventoryAdjustmentDrawerContext,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const InventoryAdjustmentDrawerContent = React.lazy(() =>
|
||||
import('./InventoryAdjustmentDrawerContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Inventory adjustment detail drawer.
|
||||
*/
|
||||
function InventoryAdjustmentDetailDrawer({
|
||||
name,
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { inventoryId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
name={name}
|
||||
style={{ minWidth: '700px', maxWidth: '900px' }}
|
||||
size={'65%'}
|
||||
>
|
||||
<DrawerSuspense>
|
||||
<InventoryAdjustmentDrawerContent inventoryId={inventoryId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDrawers())(InventoryAdjustmentDetailDrawer);
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export const useInventoryAdjustmentEntriesColumns = () =>
|
||||
React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('product_and_service'),
|
||||
accessor: 'item.name',
|
||||
width: 150,
|
||||
className: 'name',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('quantity'),
|
||||
accessor: 'quantity',
|
||||
width: 100,
|
||||
align: 'right',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('cost'),
|
||||
accessor: 'cost',
|
||||
width: 100,
|
||||
align: 'right',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('value'),
|
||||
accessor: 'value',
|
||||
width: 100,
|
||||
align: 'right',
|
||||
disableSortBy: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
36
src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetail.js
Normal file
36
src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetail.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Tab } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { DrawerMainTabs } from 'components';
|
||||
|
||||
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
|
||||
import InvoiceDetailTab from './InvoiceDetailTab';
|
||||
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
|
||||
|
||||
import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Invoice view detail.
|
||||
*/
|
||||
export default function InvoiceDetail() {
|
||||
const { transactions } = useInvoiceDetailDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(InvoiceDrawerCls.invoice_details)}>
|
||||
<DrawerMainTabs defaultSelectedTabId="details">
|
||||
<Tab
|
||||
title={intl.get('details')}
|
||||
id={'details'}
|
||||
panel={<InvoiceDetailTab />}
|
||||
/>
|
||||
<Tab
|
||||
title={intl.get('journal_entries')}
|
||||
id={'journal_entries'}
|
||||
panel={<JournalEntriesTable transactions={transactions} />}
|
||||
/>
|
||||
</DrawerMainTabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Button,
|
||||
NavbarGroup,
|
||||
Classes,
|
||||
NavbarDivider,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
|
||||
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { Icon, FormattedMessage as T } from 'components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Invoice details action bar.
|
||||
*/
|
||||
function InvoiceDetailActionsBar({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
// Invoice detail drawer context.
|
||||
const { invoiceId } = useInvoiceDetailDrawerContext();
|
||||
|
||||
// Handle edit sale invoice.
|
||||
const handleEditInvoice = () => {
|
||||
history.push(`/invoices/${invoiceId}/edit`);
|
||||
closeDrawer('invoice-detail-drawer');
|
||||
};
|
||||
|
||||
// Handle delete sale invoice.
|
||||
const handleDeleteInvoice = () => {
|
||||
openAlert('invoice-delete', { invoiceId });
|
||||
};
|
||||
|
||||
// Handle print invoices.
|
||||
const handlePrintInvoice = () => {
|
||||
openDialog('invoice-pdf-preview', { invoiceId });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={<T id={'edit_invoice'} />}
|
||||
onClick={handleEditInvoice}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="print-16" />}
|
||||
text={<T id={'print'} />}
|
||||
onClick={handlePrintInvoice}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteInvoice}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
withDrawerActions,
|
||||
withAlertsActions,
|
||||
)(InvoiceDetailActionsBar);
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { DrawerBody } from 'components';
|
||||
|
||||
import 'style/components/Drawers/ViewDetail/ViewDetail.scss';
|
||||
|
||||
import InvoiceDetail from './InvoiceDetail';
|
||||
import { InvoiceDetailDrawerProvider } from './InvoiceDetailDrawerProvider';
|
||||
|
||||
/**
|
||||
* Invoice detail drawer content.
|
||||
*/
|
||||
export default function InvoiceDetailDrawerContent({
|
||||
// #ownProp
|
||||
invoiceId,
|
||||
}) {
|
||||
return (
|
||||
<InvoiceDetailDrawerProvider invoiceId={invoiceId}>
|
||||
<DrawerBody>
|
||||
<InvoiceDetail />
|
||||
</DrawerBody>
|
||||
</InvoiceDetailDrawerProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DrawerHeaderContent, DrawerLoading } from 'components';
|
||||
import { useTransactionsByReference, useInvoice } from 'hooks/query';
|
||||
|
||||
const InvoiceDetailDrawerContext = React.createContext();
|
||||
/**
|
||||
* Invoice detail provider.
|
||||
*/
|
||||
function InvoiceDetailDrawerProvider({ invoiceId, ...props }) {
|
||||
// Handle fetch transaction by reference.
|
||||
const {
|
||||
data: { transactions },
|
||||
isLoading: isTransactionLoading,
|
||||
} = useTransactionsByReference(
|
||||
{
|
||||
reference_id: invoiceId,
|
||||
reference_type: 'SaleInvoice',
|
||||
},
|
||||
{ enabled: !!invoiceId },
|
||||
);
|
||||
|
||||
// Fetch sale invoice details.
|
||||
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId, {
|
||||
enabled: !!invoiceId,
|
||||
});
|
||||
|
||||
//provider.
|
||||
const provider = {
|
||||
transactions,
|
||||
invoiceId,
|
||||
invoice,
|
||||
};
|
||||
return (
|
||||
<DrawerLoading loading={isTransactionLoading || isInvoiceLoading}>
|
||||
<DrawerHeaderContent
|
||||
name="invoice-detail-drawer"
|
||||
title={intl.get('invoice_details')}
|
||||
/>
|
||||
<InvoiceDetailDrawerContext.Provider value={provider} {...props} />
|
||||
</DrawerLoading>
|
||||
);
|
||||
}
|
||||
|
||||
const useInvoiceDetailDrawerContext = () =>
|
||||
React.useContext(InvoiceDetailDrawerContext);
|
||||
|
||||
export { InvoiceDetailDrawerProvider, useInvoiceDetailDrawerContext };
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { T, TotalLines, FormatNumber, TotalLine } from 'components';
|
||||
import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss';
|
||||
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
|
||||
|
||||
/**
|
||||
* Invoice details footer.
|
||||
*/
|
||||
export function InvoiceDetailFooter() {
|
||||
const { invoice } = useInvoiceDetailDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(InvoiceDrawerCls.detail_panel_footer)}>
|
||||
<TotalLines>
|
||||
<TotalLine
|
||||
title={<T id={'invoice.details.subtotal'} />}
|
||||
value={<FormatNumber value={invoice.balance} />}
|
||||
className={InvoiceDrawerCls.total_line_subtotal}
|
||||
/>
|
||||
<TotalLine
|
||||
title={<T id={'invoice.details.total'} />}
|
||||
value={invoice.formatted_amount}
|
||||
className={InvoiceDrawerCls.total_line_total}
|
||||
/>
|
||||
<TotalLine
|
||||
title={<T id={'invoice.details.payment_amount'} />}
|
||||
value={invoice.formatted_payment_amount}
|
||||
className={InvoiceDrawerCls.total_line_payment}
|
||||
/>
|
||||
<TotalLine
|
||||
title={<T id={'invoice.details.due_amount'} />}
|
||||
value={invoice.formatted_due_amount}
|
||||
className={InvoiceDrawerCls.total_line_dueAmount}
|
||||
/>
|
||||
</TotalLines>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { defaultTo } from 'lodash';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { DetailsMenu, DetailItem, FormatDate } from 'components';
|
||||
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
|
||||
|
||||
import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Invoice detail header.
|
||||
*/
|
||||
export default function InvoiceDetailHeader() {
|
||||
const { invoice } = useInvoiceDetailDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(InvoiceDrawerCls.detail_panel_header)}>
|
||||
<DetailsMenu>
|
||||
<DetailItem label={intl.get('amount')}>
|
||||
<h3 class="big-number">{invoice.formatted_amount}</h3>
|
||||
</DetailItem>
|
||||
<DetailItem
|
||||
label={intl.get('invoice_no')}
|
||||
children={invoice.invoice_no}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('customer_name')}
|
||||
children={invoice.customer?.display_name}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('invoice_date')}
|
||||
children={<FormatDate value={invoice.invoice_date} />}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('due_date')}
|
||||
children={<FormatDate value={invoice.due_date} />}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
|
||||
<DetailsMenu direction={'horizantal'} minLabelSize={'140px'}>
|
||||
<DetailItem
|
||||
label={intl.get('due_amount')}
|
||||
children={invoice.formatted_due_amount}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('reference')}
|
||||
children={defaultTo(invoice.reference_no, '--')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('invoice.details.created_at')}
|
||||
children={<FormatDate value={invoice.created_at} />}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { Card } from 'components';
|
||||
|
||||
import InvoiceDetailActionsBar from './InvoiceDetailActionsBar';
|
||||
import InvoiceDetailHeader from './InvoiceDetailHeader';
|
||||
import InvoiceDetailTable from './InvoiceDetailTable';
|
||||
import { InvoiceDetailFooter } from './InvoiceDetailFooter';
|
||||
|
||||
import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Invoice readonly details tab panel.
|
||||
*/
|
||||
export default function InvoiceDetailTab() {
|
||||
return (
|
||||
<div className={clsx(InvoiceDrawerCls.detail_panel)}>
|
||||
<InvoiceDetailActionsBar />
|
||||
|
||||
<Card>
|
||||
<InvoiceDetailHeader />
|
||||
<InvoiceDetailTable />
|
||||
<InvoiceDetailFooter />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { DataTable } from 'components';
|
||||
|
||||
import { useInvoiceReadonlyEntriesColumns } from './utils';
|
||||
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
|
||||
|
||||
import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Invoice readonly details entries table columns.
|
||||
*/
|
||||
export default function InvoiceDetailTable() {
|
||||
const columns = useInvoiceReadonlyEntriesColumns();
|
||||
|
||||
const {
|
||||
invoice: { entries },
|
||||
} = useInvoiceDetailDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(InvoiceDrawerCls.detail_panel_table)}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={entries}
|
||||
className={'table-constrant'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
33
src/containers/Drawers/InvoiceDetailDrawer/index.js
Normal file
33
src/containers/Drawers/InvoiceDetailDrawer/index.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const InvoiceDetailDrawerContent = React.lazy(() =>
|
||||
import('./InvoiceDetailDrawerContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Invoice Detail drawer.
|
||||
*/
|
||||
function InvoiceDetailDrawer({
|
||||
name,
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { invoiceId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
name={name}
|
||||
style={{ minWidth: '700px', maxWidth: '900px' }}
|
||||
size={'65%'}
|
||||
>
|
||||
<DrawerSuspense>
|
||||
<InvoiceDetailDrawerContent invoiceId={invoiceId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
export default compose(withDrawers())(InvoiceDetailDrawer);
|
||||
50
src/containers/Drawers/InvoiceDetailDrawer/utils.js
Normal file
50
src/containers/Drawers/InvoiceDetailDrawer/utils.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { FormatNumberCell } from '../../../components';
|
||||
|
||||
/**
|
||||
* Retrieve invoice readonly details table columns.
|
||||
*/
|
||||
export const useInvoiceReadonlyEntriesColumns = () =>
|
||||
React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('product_and_service'),
|
||||
accessor: 'item.name',
|
||||
width: 150,
|
||||
className: 'name',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('description'),
|
||||
accessor: 'description',
|
||||
className: 'description',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('quantity'),
|
||||
accessor: 'quantity',
|
||||
Cell: FormatNumberCell,
|
||||
width: 100,
|
||||
align: 'right',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('rate'),
|
||||
accessor: 'rate',
|
||||
Cell: FormatNumberCell,
|
||||
width: 100,
|
||||
align: 'right',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: intl.get('amount'),
|
||||
accessor: 'amount',
|
||||
Cell: FormatNumberCell,
|
||||
width: 100,
|
||||
align: 'right',
|
||||
disableSortBy: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
import ItemDetailActionsBar from './ItemDetailActionsBar';
|
||||
import ItemDetailHeader from './ItemDetailHeader';
|
||||
|
||||
import { Card } from 'components';
|
||||
|
||||
/**
|
||||
* Item detail.
|
||||
*/
|
||||
export default function ItemDetail() {
|
||||
return (
|
||||
<div className="item-drawer">
|
||||
<ItemDetailActionsBar />
|
||||
|
||||
<Card>
|
||||
<ItemDetailHeader />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
Button,
|
||||
NavbarGroup,
|
||||
Classes,
|
||||
NavbarDivider,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
|
||||
import { useItemDetailDrawerContext } from './ItemDetailDrawerProvider';
|
||||
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { Icon, FormattedMessage as T } from 'components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Item action-bar of readonly details drawer.
|
||||
*/
|
||||
function ItemDetailActionsBar({
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
// Item readonly drawer context.
|
||||
const { itemId } = useItemDetailDrawerContext();
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
// Handle edit item.
|
||||
const handleEditItem = () => {
|
||||
history.push(`/items/${itemId}/edit`);
|
||||
closeDrawer('item-detail-drawer');
|
||||
};
|
||||
|
||||
// Handle delete item.
|
||||
const handleDeleteItem = () => {
|
||||
openAlert('item-delete', { itemId });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={<T id={'edit_item'} />}
|
||||
onClick={handleEditItem}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteItem}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDrawerActions,
|
||||
withAlertsActions,
|
||||
)(ItemDetailActionsBar);
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
import 'style/components/Drawers/ItemDrawer.scss';
|
||||
|
||||
import { DrawerBody } from 'components';
|
||||
import ItemContentDetails from './ItemContentDetails';
|
||||
import { ItemDetailDrawerProvider } from './ItemDetailDrawerProvider';
|
||||
|
||||
/**
|
||||
* Item detail drawer content.
|
||||
*/
|
||||
export default function ItemDetailDrawerContent({
|
||||
// #ownProp
|
||||
itemId,
|
||||
}) {
|
||||
return (
|
||||
<ItemDetailDrawerProvider itemId={itemId}>
|
||||
<DrawerBody>
|
||||
<ItemContentDetails />
|
||||
</DrawerBody>
|
||||
</ItemDetailDrawerProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { DrawerHeaderContent, DrawerLoading } from 'components';
|
||||
import { useItem } from 'hooks/query';
|
||||
|
||||
const ItemDetailDrawerContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Item detail provider
|
||||
*/
|
||||
function ItemDetailDrawerProvider({ itemId, ...props }) {
|
||||
// Fetches the given item detail.
|
||||
const { isLoading: isItemLoading, data: item } = useItem(itemId, {
|
||||
enabled: !!itemId,
|
||||
});
|
||||
|
||||
//provider.
|
||||
const provider = {
|
||||
item,
|
||||
itemId,
|
||||
isItemLoading,
|
||||
};
|
||||
|
||||
return (
|
||||
<DrawerLoading loading={isItemLoading}>
|
||||
<DrawerHeaderContent name="item-detail-drawer" title={item?.name} />
|
||||
<ItemDetailDrawerContext.Provider value={provider} {...props} />
|
||||
</DrawerLoading>
|
||||
);
|
||||
}
|
||||
const useItemDetailDrawerContext = () =>
|
||||
React.useContext(ItemDetailDrawerContext);
|
||||
|
||||
export { ItemDetailDrawerProvider, useItemDetailDrawerContext };
|
||||
82
src/containers/Drawers/ItemDetailDrawer/ItemDetailHeader.js
Normal file
82
src/containers/Drawers/ItemDetailDrawer/ItemDetailHeader.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { defaultTo } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { If, DetailsMenu, DetailItem } from 'components';
|
||||
import { useItemDetailDrawerContext } from './ItemDetailDrawerProvider';
|
||||
|
||||
/**
|
||||
* Item header drawer of readonly details.
|
||||
*/
|
||||
export default function ItemDetailHeader() {
|
||||
const { item } = useItemDetailDrawerContext();
|
||||
|
||||
return (
|
||||
<div class="item-drawer__content">
|
||||
<DetailsMenu direction={'vertical'}>
|
||||
<DetailItem
|
||||
name={'name'}
|
||||
label={intl.get('item_name')}
|
||||
children={item.name}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('sell_price')}
|
||||
children={item.sell_price_formatted}
|
||||
align={'right'}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('cost_price')}
|
||||
children={item.cost_price_formatted}
|
||||
align={'right'}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
|
||||
<DetailsMenu direction={'horizantal'}>
|
||||
<DetailItem label={intl.get('item_type')} children={item.type} />
|
||||
<DetailItem
|
||||
label={intl.get('item_code')}
|
||||
children={defaultTo(item.code, '-')}
|
||||
/>
|
||||
<If condition={item.type === 'inventory'}>
|
||||
<DetailItem name={'quantity'} label={intl.get('quantity_on_hand')}>
|
||||
<span
|
||||
className={classNames({
|
||||
mines: item.quantity_on_hand <= 0,
|
||||
plus: item.quantity_on_hand > 0,
|
||||
})}
|
||||
>
|
||||
{defaultTo(item.quantity_on_hand, '-')}
|
||||
</span>
|
||||
</DetailItem>
|
||||
</If>
|
||||
<DetailItem
|
||||
label={intl.get('category_name')}
|
||||
children={defaultTo(item.category?.name, '-')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('cost_account_id')}
|
||||
children={defaultTo(item.cost_account?.name, '-')}
|
||||
/>
|
||||
<If condition={item.type === 'inventory'}>
|
||||
<DetailItem
|
||||
label={intl.get('inventory_account')}
|
||||
children={defaultTo(item?.inventory_account?.name, '-')}
|
||||
/>
|
||||
</If>
|
||||
<DetailItem
|
||||
label={intl.get('sell_account_id')}
|
||||
children={defaultTo(item?.sell_account?.name, '-')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('item.sell_description')}
|
||||
children={defaultTo(item.sell_description, '-')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('item.purchase_description')}
|
||||
children={defaultTo(item.cost_description, '-')}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
29
src/containers/Drawers/ItemDetailDrawer/index.js
Normal file
29
src/containers/Drawers/ItemDetailDrawer/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const ItemDetailDrawerContent = React.lazy(() =>
|
||||
import('./ItemDetailDrawerContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Item Detail drawer.
|
||||
*/
|
||||
function ItemDetailDrawer({
|
||||
name,
|
||||
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { itemId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer isOpen={isOpen} name={name} size={'750px'}>
|
||||
<DrawerSuspense>
|
||||
<ItemDetailDrawerContent itemId={itemId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
export default compose(withDrawers())(ItemDetailDrawer);
|
||||
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import Icon from 'components/Icon';
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
NavbarGroup,
|
||||
Intent,
|
||||
NavbarDivider,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { useManualJournalDrawerContext } from './ManualJournalDrawerProvider';
|
||||
|
||||
/**
|
||||
* Manual journal action bar.
|
||||
*/
|
||||
function ManualJournalDrawerActionBar({
|
||||
// #withAlertsDialog
|
||||
openAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { manualJournalId } = useManualJournalDrawerContext();
|
||||
|
||||
// Handle edit manual journal action.
|
||||
const handleEditManualJournal = () => {
|
||||
history.push(`/manual-journals/${manualJournalId}/edit`);
|
||||
closeDrawer('journal-drawer');
|
||||
};
|
||||
|
||||
// Handle manual journal delete action.
|
||||
const handleDeleteManualJournal = () => {
|
||||
openAlert('journal-delete', { manualJournalId });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={<T id={'edit_journal'} />}
|
||||
onClick={handleEditManualJournal}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteManualJournal}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertsActions,
|
||||
withDrawerActions,
|
||||
)(ManualJournalDrawerActionBar);
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
import 'style/components/Drawers/ManualJournalDrawer.scss';
|
||||
|
||||
import { DrawerBody } from 'components';
|
||||
import { ManualJournalDrawerProvider } from './ManualJournalDrawerProvider';
|
||||
import ManualJournalDrawerDetails from './ManualJournalDrawerDetails';
|
||||
|
||||
/**
|
||||
* Manual Journal drawer content.
|
||||
*/
|
||||
export default function ManualJournalDrawerContent({
|
||||
// #ownProp
|
||||
manualJournalId,
|
||||
}) {
|
||||
return (
|
||||
<ManualJournalDrawerProvider manualJournalId={manualJournalId}>
|
||||
<DrawerBody>
|
||||
<ManualJournalDrawerDetails />
|
||||
</DrawerBody>
|
||||
</ManualJournalDrawerProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Card } from 'components';
|
||||
|
||||
import ManualJournalDrawerActionBar from './ManualJournalDrawerActionBar';
|
||||
import ManualJournalDrawerHeader from './ManualJournalDrawerHeader';
|
||||
import ManualJournalDrawerTable from './ManualJournalDrawerTable';
|
||||
import ManualJournalDrawerFooter from './ManualJournalDrawerFooter';
|
||||
|
||||
import { useManualJournalDrawerContext } from 'containers/Drawers/ManualJournalDrawer/ManualJournalDrawerProvider';
|
||||
|
||||
/**
|
||||
* Manual journal view details.
|
||||
*/
|
||||
export default function ManualJournalDrawerDetails() {
|
||||
const { manualJournal } = useManualJournalDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={'journal-drawer'}>
|
||||
<ManualJournalDrawerActionBar manualJournal={manualJournal} />
|
||||
|
||||
<div className="journal-drawer__content">
|
||||
<Card>
|
||||
<ManualJournalDrawerHeader />
|
||||
<ManualJournalDrawerTable />
|
||||
<ManualJournalDrawerFooter />
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { useManualJournalDrawerContext } from './ManualJournalDrawerProvider';
|
||||
|
||||
import { FormatNumber } from '../../../components';
|
||||
|
||||
/**
|
||||
* Manual journal readonly details footer.
|
||||
*/
|
||||
export default function ManualJournalDrawerFooter() {
|
||||
const {
|
||||
manualJournal: { amount, formatted_amount },
|
||||
} = useManualJournalDrawerContext();
|
||||
|
||||
return (
|
||||
<div className="journal-drawer__content-footer">
|
||||
<div class="total-lines">
|
||||
<div class="total-lines__line total-lines__line--subtotal">
|
||||
<div class="title">Subtotal</div>
|
||||
<div class="debit">
|
||||
<FormatNumber value={amount} />
|
||||
</div>
|
||||
<div class="credit">
|
||||
<FormatNumber value={amount} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="total-lines__line total-lines__line--total">
|
||||
<div class="title">TOTAL</div>
|
||||
<div class="debit">{formatted_amount}</div>
|
||||
<div class="credit">{formatted_amount}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { defaultTo } from 'lodash';
|
||||
import { DetailsMenu, DetailItem, FormattedMessage as T } from 'components';
|
||||
import { useManualJournalDrawerContext } from './ManualJournalDrawerProvider';
|
||||
|
||||
/**
|
||||
* Manual journal details header.
|
||||
*/
|
||||
export default function ManualJournalDrawerHeader() {
|
||||
const {
|
||||
manualJournal: {
|
||||
formatted_amount,
|
||||
journal_type,
|
||||
journal_number,
|
||||
reference,
|
||||
currency_code,
|
||||
description,
|
||||
},
|
||||
} = useManualJournalDrawerContext();
|
||||
|
||||
return (
|
||||
<div className={'journal-drawer__content-header'}>
|
||||
<DetailsMenu>
|
||||
<DetailItem name={'total'} label={<T id={'total'} />}>
|
||||
<h3 class="amount">{formatted_amount}</h3>
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'journal-type'} label={<T id={'journal_type'} />}>
|
||||
{journal_type}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'journal-number'} label={<T id={'journal_no'} />}>
|
||||
{journal_number}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'reference-no'} label={<T id={'reference_no'} />}>
|
||||
{defaultTo(reference, '-')}
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem name={'currency'} label={<T id={'currency'} />}>
|
||||
{currency_code}
|
||||
</DetailItem>
|
||||
</DetailsMenu>
|
||||
|
||||
<div class="journal-drawer__content-description">
|
||||
<b class="title">Description</b>: {defaultTo(description, '—')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { useJournal } from 'hooks/query';
|
||||
import { DrawerLoading, DrawerHeaderContent } from 'components';
|
||||
|
||||
const ManualJournalDrawerContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Manual journal drawer provider.
|
||||
*/
|
||||
function ManualJournalDrawerProvider({ manualJournalId, ...props }) {
|
||||
// Fetch the specific manual journal details.
|
||||
const {
|
||||
data: manualJournal,
|
||||
isLoading: isJournalLoading,
|
||||
isFetching: isJournalFetching,
|
||||
} = useJournal(manualJournalId, {
|
||||
enabled: !!manualJournalId,
|
||||
});
|
||||
|
||||
// Provider.
|
||||
const provider = {
|
||||
manualJournalId,
|
||||
manualJournal,
|
||||
|
||||
isJournalFetching,
|
||||
isJournalLoading,
|
||||
};
|
||||
|
||||
return (
|
||||
<DrawerLoading loading={isJournalLoading}>
|
||||
<DrawerHeaderContent
|
||||
name={'journal-drawer'}
|
||||
title={intl.get('manual_journal_number', {
|
||||
number: manualJournal?.journal_number,
|
||||
})}
|
||||
/>
|
||||
<ManualJournalDrawerContext.Provider value={provider} {...props} />
|
||||
</DrawerLoading>
|
||||
);
|
||||
}
|
||||
|
||||
const useManualJournalDrawerContext = () =>
|
||||
React.useContext(ManualJournalDrawerContext);
|
||||
|
||||
export { ManualJournalDrawerProvider, useManualJournalDrawerContext };
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
|
||||
import { DataTable, If } from 'components';
|
||||
import { useManualJournalEntriesColumns } from './utils';
|
||||
import { useManualJournalDrawerContext } from './ManualJournalDrawerProvider';
|
||||
|
||||
/**
|
||||
* Manual journal drawer table.
|
||||
*/
|
||||
export default function ManualJournalDrawerTable() {
|
||||
const columns = useManualJournalEntriesColumns();
|
||||
const {
|
||||
manualJournal: { entries, description },
|
||||
} = useManualJournalDrawerContext();
|
||||
|
||||
return (
|
||||
<div className="journal-drawer__content-table">
|
||||
<DataTable columns={columns} data={entries} />
|
||||
|
||||
<If condition={description}>
|
||||
<p className={'desc'}>
|
||||
<b>Description</b>: {description}
|
||||
</p>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
35
src/containers/Drawers/ManualJournalDrawer/index.js
Normal file
35
src/containers/Drawers/ManualJournalDrawer/index.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import React, { lazy } from 'react';
|
||||
import { Drawer, DrawerSuspense } from 'components';
|
||||
import withDrawers from 'containers/Drawer/withDrawers';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const ManualJournalDrawerContent = lazy(() =>
|
||||
import('./ManualJournalDrawerContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Manual journal drawer.
|
||||
*/
|
||||
function ManualJournalDrawer({
|
||||
name,
|
||||
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { manualJournalId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
name={name}
|
||||
size={'65%'}
|
||||
style={{ minWidth: '700px', maxWidth: '900px' }}
|
||||
>
|
||||
<DrawerSuspense>
|
||||
<ManualJournalDrawerContent manualJournalId={manualJournalId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDrawers())(ManualJournalDrawer);
|
||||
76
src/containers/Drawers/ManualJournalDrawer/utils.js
Normal file
76
src/containers/Drawers/ManualJournalDrawer/utils.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import intl from 'react-intl-universal';
|
||||
import React from 'react';
|
||||
import { Classes, Tooltip, Position } from '@blueprintjs/core';
|
||||
|
||||
import { FormatNumberCell, If, Icon } from '../../../components';
|
||||
|
||||
/**
|
||||
* Note column accessor.
|
||||
*/
|
||||
export function NoteAccessor(row) {
|
||||
return (
|
||||
<If condition={row.note}>
|
||||
<Tooltip
|
||||
className={Classes.TOOLTIP_INDICATOR}
|
||||
content={row.note}
|
||||
position={Position.LEFT_TOP}
|
||||
hoverOpenDelay={50}
|
||||
>
|
||||
<Icon icon={'file-alt'} iconSize={16} />
|
||||
</Tooltip>
|
||||
</If>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve read-only manual journal entries columns.
|
||||
*/
|
||||
export const useManualJournalEntriesColumns = () =>
|
||||
React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('account_name'),
|
||||
accessor: 'account.name',
|
||||
width: 130,
|
||||
disableSortBy: true,
|
||||
className: 'account',
|
||||
},
|
||||
{
|
||||
Header: intl.get('contact'),
|
||||
accessor: 'contact.display_name',
|
||||
width: 130,
|
||||
disableSortBy: true,
|
||||
className: 'contact',
|
||||
},
|
||||
{
|
||||
Header: intl.get('note'),
|
||||
accessor: NoteAccessor,
|
||||
width: 80,
|
||||
disableSortBy: true,
|
||||
className: 'note',
|
||||
},
|
||||
{
|
||||
Header: intl.get('credit'),
|
||||
accessor: 'credit',
|
||||
Cell: FormatNumberCell,
|
||||
width: 100,
|
||||
disableResizable: true,
|
||||
disableSortBy: true,
|
||||
formatNumber: { noZero: true },
|
||||
className: 'credit',
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
Header: intl.get('debit'),
|
||||
accessor: 'debit',
|
||||
Cell: FormatNumberCell,
|
||||
width: 100,
|
||||
disableResizable: true,
|
||||
disableSortBy: true,
|
||||
formatNumber: { noZero: true },
|
||||
className: 'debit',
|
||||
align: 'right',
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
51
src/containers/Drawers/PaperTemplate/PaperTemplate.js
Normal file
51
src/containers/Drawers/PaperTemplate/PaperTemplate.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
|
||||
import 'style/components/Drawers/DrawerTemplate.scss';
|
||||
|
||||
import PaperTemplateHeader from './PaperTemplateHeader';
|
||||
import PaperTemplateTable from './PaperTemplateTable';
|
||||
import PaperTemplateFooter from './PaperTemplateFooter';
|
||||
import { updateItemsEntriesTotal } from 'containers/Entries/utils';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
|
||||
function PaperTemplate({ labels: propLabels, paperData, entries }) {
|
||||
const labels = {
|
||||
name: intl.get('estimate_'),
|
||||
billedTo: intl.get('billed_to'),
|
||||
date: intl.get('estimate_date'),
|
||||
refNo: intl.get('estimate_no'),
|
||||
billedFrom: intl.get('billed_from'),
|
||||
amount: intl.get('estimate_amount'),
|
||||
dueDate: intl.get('due_date_'),
|
||||
...propLabels,
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
billedTo: paperData.customer.display_name,
|
||||
date: paperData.estimate_date,
|
||||
amount: '',
|
||||
billedFrom: '',
|
||||
dueDate: paperData.expiration_date,
|
||||
referenceNo: paperData.estimate_number,
|
||||
...paperData,
|
||||
};
|
||||
|
||||
return (
|
||||
<div id={'page-size'}>
|
||||
<div className={'template'}>
|
||||
<PaperTemplateHeader
|
||||
defaultLabels={labels}
|
||||
headerData={defaultValues}
|
||||
/>
|
||||
<PaperTemplateTable
|
||||
tableData={updateItemsEntriesTotal(entries)}
|
||||
currencyCode={paperData.currency_code}
|
||||
/>
|
||||
<PaperTemplateFooter footerData={defaultValues} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PaperTemplate;
|
||||
23
src/containers/Drawers/PaperTemplate/PaperTemplateFooter.js
Normal file
23
src/containers/Drawers/PaperTemplate/PaperTemplateFooter.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { If } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export default function PaperTemplateFooter({
|
||||
footerData: { terms_conditions },
|
||||
}) {
|
||||
return (
|
||||
<div className="template__terms">
|
||||
<If condition={terms_conditions}>
|
||||
<div className="template__terms__title">
|
||||
<h4>{intl.get('conditions_and_terms')}</h4>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
{[terms_conditions].map((terms) => (
|
||||
<li>{terms}</li>
|
||||
))}
|
||||
</ul>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
src/containers/Drawers/PaperTemplate/PaperTemplateHeader.js
Normal file
23
src/containers/Drawers/PaperTemplate/PaperTemplateHeader.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { TemplateHeader, TemplateContent } from '../components';
|
||||
|
||||
export default function PaperTemplateHeader({
|
||||
defaultLabels,
|
||||
headerData: { referenceNo, amount, dueDate, date, billedTo, currency_code },
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<TemplateHeader defaultLabels={defaultLabels} />
|
||||
<TemplateContent
|
||||
defaultLabels={defaultLabels}
|
||||
billedTo={billedTo}
|
||||
date={date}
|
||||
referenceNo={referenceNo}
|
||||
amount={amount}
|
||||
billedFrom={''}
|
||||
dueDate={dueDate}
|
||||
currencyCode={currency_code}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
43
src/containers/Drawers/PaperTemplate/PaperTemplateTable.js
Normal file
43
src/containers/Drawers/PaperTemplate/PaperTemplateTable.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DataTable, Money } from 'components';
|
||||
|
||||
export default function DrawerTemplateTable({ tableData, currencyCode }) {
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('description'),
|
||||
accessor: 'description',
|
||||
disableSortBy: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
Header: intl.get('rate'),
|
||||
accessor: 'rate',
|
||||
accessor: ({ rate }) => <Money amount={rate} currency={currencyCode} />,
|
||||
disableSortBy: true,
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
Header: intl.get('qty'),
|
||||
accessor: 'quantity',
|
||||
disableSortBy: true,
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
Header: intl.get('total'),
|
||||
accessor: ({ total }) => (
|
||||
<Money amount={total} currency={currencyCode} />
|
||||
),
|
||||
disableSortBy: true,
|
||||
width: 70,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
return (
|
||||
<div className="template__table">
|
||||
<DataTable columns={columns} data={tableData} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Button,
|
||||
NavbarGroup,
|
||||
Classes,
|
||||
NavbarDivider,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
|
||||
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { Icon, FormattedMessage as T } from 'components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Payment made - Details panel - actions bar.
|
||||
*/
|
||||
function PaymentMadeDetailActionsBar({
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
const { paymentMadeId } = usePaymentMadeDetailContext();
|
||||
|
||||
// Handle edit payment made.
|
||||
const handleEditPaymentMade = () => {
|
||||
history.push(`/payment-mades/${paymentMadeId}/edit`);
|
||||
closeDrawer('payment-made-detail-drawer');
|
||||
};
|
||||
|
||||
// Handle delete payment made.
|
||||
const handleDeletePaymentMade = () => {
|
||||
openAlert('payment-made-delete', { paymentMadeId });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={<T id={'edit_payment_made'} />}
|
||||
onClick={handleEditPaymentMade}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeletePaymentMade}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
withDrawerActions,
|
||||
withAlertsActions,
|
||||
)(PaymentMadeDetailActionsBar);
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { DrawerBody } from 'components';
|
||||
|
||||
import 'style/components/Drawers/ViewDetail/ViewDetail.scss';
|
||||
|
||||
import PaymentMadeDetails from './PaymentMadeDetails';
|
||||
import { PaymentMadeDetailProvider } from './PaymentMadeDetailProvider';
|
||||
|
||||
/**
|
||||
* Payment made detail content.
|
||||
*/
|
||||
export default function PaymentMadeDetailContent({
|
||||
// #ownProp
|
||||
paymentMadeId,
|
||||
}) {
|
||||
return (
|
||||
<PaymentMadeDetailProvider paymentMadeId={paymentMadeId}>
|
||||
<DrawerBody>
|
||||
<PaymentMadeDetails />
|
||||
</DrawerBody>
|
||||
</PaymentMadeDetailProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { T, TotalLines, TotalLine } from 'components';
|
||||
import PaymentDrawerCls from './PaymentMadeDrawer.module.scss';
|
||||
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
|
||||
|
||||
/**
|
||||
* Payment made - Details panel - Footer.
|
||||
*/
|
||||
export default function PaymentMadeDetailFooter() {
|
||||
const { paymentMade } = usePaymentMadeDetailContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(PaymentDrawerCls.detail_panel_footer)}>
|
||||
<TotalLines>
|
||||
<TotalLine
|
||||
title={<T id={'payment_made.details.subtotal'} />}
|
||||
value={paymentMade.amount}
|
||||
className={clsx(PaymentDrawerCls.total_line_subtotal)}
|
||||
/>
|
||||
<TotalLine
|
||||
title={<T id={'payment_made.details.total'} />}
|
||||
value={paymentMade.formatted_amount}
|
||||
className={clsx(PaymentDrawerCls.total_line_total)}
|
||||
/>
|
||||
</TotalLines>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import clsx from 'classnames';
|
||||
import { defaultTo } from 'lodash';
|
||||
|
||||
import { DetailsMenu, DetailItem, FormatDate } from 'components';
|
||||
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
|
||||
|
||||
import PaymentDrawerCls from './PaymentMadeDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Payment made - detail panel - header.
|
||||
*/
|
||||
export default function PaymentMadeDetailHeader() {
|
||||
const { paymentMade } = usePaymentMadeDetailContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(PaymentDrawerCls.detail_panel_header)}>
|
||||
<DetailsMenu>
|
||||
<DetailItem label={intl.get('amount')}>
|
||||
<h3 class="big-number" children={paymentMade.formatted_amount} />
|
||||
</DetailItem>
|
||||
<DetailItem
|
||||
label={intl.get('payment_made.details.payment_number')}
|
||||
children={defaultTo(paymentMade.payment_number, '-')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('customer_name')}
|
||||
children={paymentMade.vendor?.display_name}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('payment_account')}
|
||||
children={paymentMade.payment_account?.name}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('payment_date')}
|
||||
children={<FormatDate value={paymentMade.payment_date} />}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
|
||||
<DetailsMenu direction={'horizantal'} minLabelSize={'160px'}>
|
||||
<DetailItem
|
||||
label={intl.get('description')}
|
||||
children={defaultTo(paymentMade.statement, '-')}
|
||||
/>
|
||||
<DetailItem
|
||||
label={intl.get('created_at')}
|
||||
children={<FormatDate value={paymentMade.created_at} />}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DrawerHeaderContent, DrawerLoading } from 'components';
|
||||
import {
|
||||
useTransactionsByReference,
|
||||
usePaymentMade,
|
||||
usePaymentMadeEditPage,
|
||||
} from 'hooks/query';
|
||||
|
||||
const PaymentMadeDetailContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Payment made detail provider.
|
||||
*/
|
||||
function PaymentMadeDetailProvider({ paymentMadeId, ...props }) {
|
||||
// Handle fetch specific payment made details.
|
||||
const { data: paymentMade, isLoading: isPaymentMadeLoading } =
|
||||
usePaymentMade(paymentMadeId, {
|
||||
enabled: !!paymentMadeId,
|
||||
});
|
||||
|
||||
// Handle fetch specific payment made details.
|
||||
const {
|
||||
data: { entries: paymentEntries },
|
||||
isLoading: isPaymentLoading,
|
||||
} = usePaymentMadeEditPage(paymentMadeId, {
|
||||
enabled: !!paymentMadeId,
|
||||
});
|
||||
|
||||
// Handle fetch transaction by reference.
|
||||
const {
|
||||
data: { transactions },
|
||||
isLoading: isTransactionLoading,
|
||||
} = useTransactionsByReference(
|
||||
{
|
||||
reference_id: paymentMadeId,
|
||||
reference_type: 'paymentMade',
|
||||
},
|
||||
{ enabled: !!paymentMadeId },
|
||||
);
|
||||
|
||||
//provider.
|
||||
const provider = {
|
||||
transactions,
|
||||
paymentMadeId,
|
||||
paymentMade,
|
||||
paymentEntries,
|
||||
};
|
||||
|
||||
const loading =
|
||||
isTransactionLoading || isPaymentMadeLoading || isPaymentLoading;
|
||||
|
||||
return (
|
||||
<DrawerLoading loading={loading}>
|
||||
<DrawerHeaderContent
|
||||
name="payment-made-detail-drawer"
|
||||
title={intl.get('payment_made_details')}
|
||||
/>
|
||||
<PaymentMadeDetailContext.Provider value={provider} {...props} />
|
||||
</DrawerLoading>
|
||||
);
|
||||
}
|
||||
|
||||
const usePaymentMadeDetailContext = () =>
|
||||
React.useContext(PaymentMadeDetailContext);
|
||||
export { PaymentMadeDetailProvider, usePaymentMadeDetailContext };
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { Card } from 'components';
|
||||
|
||||
import PaymentMadeDetailActionsBar from './PaymentMadeDetailActionsBar';
|
||||
import PaymentMadeDetailHeader from './PaymentMadeDetailHeader';
|
||||
import PaymentMadeDetailTable from './PaymentMadeDetailTable';
|
||||
import PaymentMadeDetailFooter from './PaymentMadeDetailFooter';
|
||||
|
||||
import PaymentDrawerCls from './PaymentMadeDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Payment made detail tab.
|
||||
*/
|
||||
export default function PaymentMadeDetailTab() {
|
||||
return (
|
||||
<div className={clsx(PaymentDrawerCls.detail_panel)}>
|
||||
<PaymentMadeDetailActionsBar />
|
||||
|
||||
<Card>
|
||||
<PaymentMadeDetailHeader />
|
||||
<PaymentMadeDetailTable />
|
||||
<PaymentMadeDetailFooter />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { usePaymentMadeEntriesColumns } from './utils';
|
||||
import { DataTable } from 'components';
|
||||
|
||||
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
|
||||
|
||||
import PaymentDrawerCls from './PaymentMadeDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Payment made read-only details table.
|
||||
*/
|
||||
export default function PaymentMadeDetailTable() {
|
||||
|
||||
// Retrieve payment made entries columns.
|
||||
const columns = usePaymentMadeEntriesColumns();
|
||||
|
||||
// Payment made details context.
|
||||
const { paymentEntries } = usePaymentMadeDetailContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(PaymentDrawerCls.detail_panel_table)}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={paymentEntries}
|
||||
className={'table-constrant'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Tab } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { DrawerMainTabs } from 'components';
|
||||
import PaymentMadeDetailTab from './PaymentMadeDetailTab';
|
||||
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
|
||||
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
|
||||
|
||||
import PaymentDrawerCls from './PaymentMadeDrawer.module.scss';
|
||||
|
||||
/**
|
||||
* Payment made - view detail.
|
||||
*/
|
||||
export default function PaymentMadeDetails() {
|
||||
const { transactions } = usePaymentMadeDetailContext();
|
||||
|
||||
return (
|
||||
<div className={clsx(PaymentDrawerCls.root)}>
|
||||
<DrawerMainTabs defaultSelectedTabId="details">
|
||||
<Tab
|
||||
id={'details'}
|
||||
title={intl.get('details')}
|
||||
panel={<PaymentMadeDetailTab />}
|
||||
/>
|
||||
<Tab
|
||||
id={'journal_entries'}
|
||||
title={intl.get('journal_entries')}
|
||||
panel={<JournalEntriesTable transactions={transactions} />}
|
||||
/>
|
||||
</DrawerMainTabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
.root {}
|
||||
|
||||
.detail_panel {
|
||||
:global .card {
|
||||
padding: 22px 15px;
|
||||
}
|
||||
|
||||
&_header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
&_table {
|
||||
:global .bigcapital-datatable {
|
||||
|
||||
.thead,
|
||||
.tbody {
|
||||
.bill_amount,
|
||||
.amount_due,
|
||||
.payment_amount {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&_footer {
|
||||
display: flex;
|
||||
|
||||
:global .total_lines {
|
||||
margin-left: auto;
|
||||
}
|
||||
:global .total_lines_line {
|
||||
|
||||
.amount,
|
||||
.title {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.amount {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.total_line {
|
||||
&_subtotal {
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
&_total {
|
||||
border-bottom: 3px double #000;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user