Merge branch 'develop'

This commit is contained in:
a.bouhuolia
2022-01-03 11:46:00 +02:00
512 changed files with 20008 additions and 2564 deletions

View File

@@ -25,7 +25,7 @@ export default function AccountDrawerHeader() {
</DetailItem>
<DetailItem name={'account-normal'} label={<T id={'account_normal'} />}>
{account.account_normal}
{account.account_normal_formatted}
<Icon
iconSize={14}
icon={`arrow-${

View File

@@ -1,26 +1,24 @@
import React from 'react';
import { Link } from 'react-router-dom';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { useAccountDrawerContext } from './AccountDrawerProvider';
import { DataTable, If } from 'components';
import { Card, DataTable, If } from 'components';
import { compose } from 'utils';
import { useAccountReadEntriesColumns } from './utils';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { TableStyle } from '../../../common';
import { useAppIntlContext } from 'components/AppIntlProvider';
/**
* account drawer table.
*/
function AccountDrawerTable({ closeDrawer }) {
const {
account,
accounts,
drawerName,
} = useAccountDrawerContext();
const { account, accounts, drawerName } = useAccountDrawerContext();
// Account read-only entries table columns.
const columns = useAccountReadEntriesColumns();
@@ -29,23 +27,38 @@ function AccountDrawerTable({ closeDrawer }) {
const handleLinkClick = () => {
closeDrawer(drawerName);
};
// Application intl context.
const { isRTL } = useAppIntlContext();
return (
<div className={'account-drawer__table'}>
<DataTable columns={columns} data={accounts} payload={{ account }}/>
<Card>
<DataTable
columns={columns}
data={accounts}
payload={{ account }}
styleName={TableStyle.Constrant}
/>
<If condition={accounts.length > 0}>
<div class="account-drawer__table-footer">
<TableFooter>
<Link
to={`/financial-reports/general-ledger`}
onClick={handleLinkClick}
>
{intl.get('view_more_transactions')}
{isRTL ? '→' : '←'} {intl.get('view_more_transactions')}
</Link>
</div>
</TableFooter>
</If>
</div>
</Card>
);
}
export default compose(withDrawerActions)(AccountDrawerTable);
const TableFooter = styled.div`
padding: 6px 14px;
display: block;
border-top: 1px solid #d2dde2;
border-bottom: 1px solid #d2dde2;
font-size: 12px;
`;

View File

@@ -8,7 +8,6 @@ import {
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { useBillDrawerContext } from './BillDrawerProvider';
@@ -16,12 +15,19 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { Can, If, Icon, FormattedMessage as T } from 'components';
import {
Can,
If,
Icon,
DrawerActionsBar,
FormattedMessage as T,
} from 'components';
import {
BillAction,
PaymentMadeAction,
AbilitySubject,
} from '../../../common/abilityOption';
import { BillMenuItem } from './utils';
import { safeCallback, compose } from 'utils';
@@ -45,6 +51,14 @@ function BillDetailActionsBar({
closeDrawer('bill-drawer');
};
// Handle convert to vendor credit.
const handleConvertToVendorCredit = () => {
history.push(`/vendor-credits/new?from_bill_id=${billId}`, {
billId: billId,
});
closeDrawer('bill-drawer');
};
// Handle delete bill.
const onDeleteBill = () => {
openAlert('bill-delete', { billId });
@@ -55,8 +69,13 @@ function BillDetailActionsBar({
openDialog('quick-payment-made', { billId });
};
// Handle allocate landed cost button click.
const handleAllocateCostClick = () => {
openDialog('allocate-landed-cost', { billId });
};
return (
<DashboardActionsBar>
<DrawerActionsBar>
<NavbarGroup>
<Can I={BillAction.Edit} a={AbilitySubject.Bill}>
<Button
@@ -71,7 +90,7 @@ function BillDetailActionsBar({
<If condition={bill.is_open && !bill.is_fully_paid}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="quick-payment-16" iconSize={16} />}
icon={<Icon icon="arrow-upward" iconSize={16} />}
text={<T id={'add_payment'} />}
onClick={handleQuickBillPayment}
/>
@@ -87,8 +106,17 @@ function BillDetailActionsBar({
onClick={safeCallback(onDeleteBill)}
/>
</Can>
<Can I={BillAction.Edit} a={AbilitySubject.Bill}>
<NavbarDivider />
<BillMenuItem
payload={{
onConvert: handleConvertToVendorCredit,
onAllocateLandedCost: handleAllocateCostClick,
}}
/>
</Can>
</NavbarGroup>
</DashboardActionsBar>
</DrawerActionsBar>
);
}

View File

@@ -1,41 +1,27 @@
import React from 'react';
import clsx from 'classnames';
import {
CommercialDocFooter,
T,
If,
DetailsMenu,
DetailItem,
} from 'components';
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.
* Bill detail footer.
* @returns {React.JSX}
*/
export function BillDetailFooter() {
export default 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>
<CommercialDocFooter>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<If condition={bill.note}>
<DetailItem label={<T id={'note'} />}>{bill.note}</DetailItem>
</If>
</DetailsMenu>
</CommercialDocFooter>
);
}

View File

@@ -1,12 +1,22 @@
import React from 'react';
import intl from 'react-intl-universal';
import { defaultTo } from 'lodash';
import clsx from 'classnames';
import styled from 'styled-components';
import { FormatDate, DetailsMenu, DetailItem } from 'components';
import {
FormatDate,
DetailsMenu,
DetailItem,
ButtonLink,
Row,
Col,
CommercialDocHeader,
CommercialDocTopHeader,
VendorDrawerLink,
} from 'components';
import { useBillDrawerContext } from './BillDrawerProvider';
import BillDrawerCls from 'style/components/Drawers/BillDrawer.module.scss';
import { BillDetailsStatus } from './utils';
/**
* Bill detail header.
@@ -15,44 +25,67 @@ 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>
<CommercialDocHeader>
<CommercialDocTopHeader>
<DetailsMenu>
<AmountDetailItem label={intl.get('amount')}>
<h3 class="big-number">{bill.formatted_amount}</h3>
</AmountDetailItem>
<StatusDetailItem>
<BillDetailsStatus bill={bill} />
</StatusDetailItem>
</DetailsMenu>
</CommercialDocTopHeader>
<Row>
<Col xs={6}>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem label={intl.get('bill_date')}>
<FormatDate value={bill.bill_date} />
</DetailItem>
<DetailItem label={intl.get('due_date')}>
<FormatDate value={bill.due_date} />
</DetailItem>
<DetailItem label={intl.get('vendor_name')}>
<VendorDrawerLink vendorId={bill.vendor_id}>
{bill.vendor?.display_name}
</VendorDrawerLink>
</DetailItem>
<DetailItem label={intl.get('bill.details.bill_number')}>
{defaultTo(bill.bill_number, '-')}
</DetailItem>
</DetailsMenu>
</Col>
<Col xs={6}>
<DetailsMenu
direction={'horizantal'}
minLabelSize={'140px'}
textAlign={'right'}
>
<DetailItem label={intl.get('due_amount')}>
<strong>{bill.formatted_due_amount}</strong>
</DetailItem>
<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>
</Col>
</Row>
</CommercialDocHeader>
);
}
const StatusDetailItem = styled(DetailItem)`
width: 50%;
text-align: right;
position: relative;
top: -5px;
`;
const AmountDetailItem = styled(DetailItem)`
width: 50%;
`;

View File

@@ -1,29 +1,22 @@
import React from 'react';
import clsx from 'classnames';
import { Card } from 'components';
import { CommercialDocBox } 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';
import { BillDetailTableFooter } from './BillDetailTableFooter';
import BillDetailFooter from './BillDetailFooter';
/**
* Bill detail panel tab.
*/
export default function BillDetailTab() {
return (
<div className={clsx(BillDrawerCls.detail_panel)}>
<BillDetailActionsBar />
<Card>
<BillDetailHeader />
<BillDetailTable />
<BillDetailFooter />
</Card>
</div>
<CommercialDocBox>
<BillDetailHeader />
<BillDetailTable />
<BillDetailTableFooter />
<BillDetailFooter />
</CommercialDocBox>
);
}

View File

@@ -1,26 +1,25 @@
import React from 'react';
import clsx from 'classnames';
import { DataTable } from 'components';
import { CommercialDocEntriesTable } from 'components';
import { useBillDrawerContext } from './BillDrawerProvider';
import { useBillReadonlyEntriesTableColumns } from './utils';
import BillDrawerCls from 'style/components/Drawers/BillDrawer.module.scss';
import { TableStyle } from '../../../common';
export default function BillDetailTable() {
const { bill: { entries } } = useBillDrawerContext();
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>
<CommercialDocEntriesTable
columns={columns}
data={entries}
styleName={TableStyle.Constrant}
/>
);
}

View File

@@ -0,0 +1,51 @@
import React from 'react';
import styled from 'styled-components';
import {
TotalLineBorderStyle,
TotalLineTextStyle,
FormatNumber,
T,
TotalLines,
TotalLine,
} from '../../../components';
import { useBillDrawerContext } from './BillDrawerProvider';
/**
* Bill read-only details table footer.
*/
export function BillDetailTableFooter() {
const { bill } = useBillDrawerContext();
return (
<BillDetailsFooterRoot>
<BillTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine
title={<T id={'bill.details.subtotal'} />}
value={<FormatNumber value={bill.amont} />}
borderStyle={TotalLineBorderStyle.SingleDark}
/>
<TotalLine
title={<T id={'bill.details.total'} />}
value={bill.formatted_amount}
borderStyle={TotalLineBorderStyle.DoubleDark}
textStyle={TotalLineTextStyle.Bold}
/>
<TotalLine
title={<T id={'bill.details.payment_amount'} />}
value={bill.formatted_payment_amount}
/>
<TotalLine
title={<T id={'bill.details.due_amount'} />}
value={bill.formatted_due_amount}
/>
</BillTotalLines>
</BillDetailsFooterRoot>
);
}
export const BillDetailsFooterRoot = styled.div``;
export const BillTotalLines = styled(TotalLines)`
margin-left: auto;
`;

View File

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

View File

@@ -1,11 +1,8 @@
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.
@@ -18,7 +15,6 @@ export default function BillDrawerContent({
<BillDrawerProvider billId={billId}>
<DrawerBody>
<BillDrawerDetails />
<BillDrawerAlerts />
</DrawerBody>
</BillDrawerProvider>
);

View File

@@ -1,49 +1,67 @@
import React from 'react';
import { Tab } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import styled from 'styled-components';
import { DrawerMainTabs } from 'components';
import {
PaymentMadeAction,
AbilitySubject,
} from '../../../common/abilityOption';
import { useAbilityContext } from 'hooks/utils';
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';
import BillGLEntriesTable from './BillGLEntriesTable';
import BillPaymentTransactionTable from './BillPaymentTransactions/BillPaymentTransactionTable';
import BillDetailActionsBar from './BillDetailActionsBar';
/**
* Bill view details.
* Bill details tabs.
*/
export default function BillDrawerDetails() {
const {
data: { transactions },
} = useBillDrawerContext();
function BillDetailsTabs() {
const ability = useAbilityContext();
return (
<div className={clsx(BillDrawerCls.root)}>
<DrawerMainTabs defaultSelectedTabId="details">
<DrawerMainTabs
renderActiveTabPanelOnly={true}
defaultSelectedTabId="details"
>
<Tab
title={intl.get('overview')}
id={'details'}
panel={<BillDetailTab />}
/>
<Tab
title={intl.get('journal_entries')}
id={'journal_entries'}
panel={<BillGLEntriesTable />}
/>
{ability.can(PaymentMadeAction.View, AbilitySubject.PaymentMade) && (
<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 />}
/>
{/* <Tab
title={intl.get('payment_transactions')}
id={'payment_transactions'}
// panel={}
/> */}
</DrawerMainTabs>
</div>
panel={<BillPaymentTransactionTable />}
/>
)}
<Tab
title={intl.get('located_landed_cost')}
id={'landed_cost'}
panel={<LocatedLandedCostTable />}
/>
</DrawerMainTabs>
);
}
/**
* Bill view detail.
*/
export default function BillDetails() {
return (
<BillDetailsRoot>
<BillDetailActionsBar />
<BillDetailsTabs />
</BillDetailsRoot>
);
}
export const BillDetailsRoot = styled.div``;

View File

@@ -1,11 +1,7 @@
import React from 'react';
import intl from 'react-intl-universal';
import { DrawerHeaderContent, DrawerLoading } from 'components';
import {
useBill,
useTransactionsByReference,
useBillLocatedLandedCost,
} from 'hooks/query';
import { useBill, useBillLocatedLandedCost } from 'hooks/query';
const BillDrawerContext = React.createContext();
@@ -18,15 +14,6 @@ function BillDrawerProvider({ billId, ...props }) {
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, {
@@ -35,19 +22,20 @@ function BillDrawerProvider({ billId, ...props }) {
//provider.
const provider = {
transactions,
billId,
data,
transactions,
bill,
};
const loading = isLandedCostLoading || isTransactionLoading || isBillLoading;
const loading = isLandedCostLoading || isBillLoading;
return (
<DrawerLoading loading={loading}>
<DrawerHeaderContent
name="bill-drawer"
title={intl.get('bill_details')}
title={intl.get('bill.drawer.title', {
number: bill.bill_number ? `(${bill.bill_number})` : null,
})}
/>
<BillDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>

View File

@@ -0,0 +1,49 @@
import React from 'react';
import styled from 'styled-components';
import { Card } from 'components';
import { useTransactionsByReference } from 'hooks/query';
import { useBillDrawerContext } from './BillDrawerProvider';
import JournalEntriesTable, {
AmountDisplayedBaseCurrencyMessage,
} from '../../JournalEntriesTable/JournalEntriesTable';
/**
* Bill GL entries table.
* @returns {React.JSX}
*/
export default function BillGLEntriesTable() {
const { billId } = useBillDrawerContext();
// Handle fetch transaction by reference.
const {
data: { transactions },
isLoading: isTransactionLoading,
} = useTransactionsByReference(
{
reference_id: billId,
reference_type: 'Bill',
},
{ enabled: !!billId },
);
return (
<BilleGLEntriesRoot>
<AmountDisplayedBaseCurrencyMessage />
<BillGLEntriesDatatable
loading={isTransactionLoading}
transactions={transactions}
/>
</BilleGLEntriesRoot>
);
}
const BilleGLEntriesRoot = styled(Card)``;
const BillGLEntriesDatatable = styled(JournalEntriesTable)`
.table .tbody .tr:last-child .td {
border-bottom: 0;
}
`;

View File

@@ -0,0 +1,78 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { DataTable, Card } from 'components';
import { useBillPaymentTransactionsColumns, ActionsMenu } from './components';
import { useBillDrawerContext } from '../BillDrawerProvider';
import { useBillPaymentTransactions } from 'hooks/query';
import { TableStyle } from '../../../../common';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
/**
* Bill payment transactions datatable.
*/
function BillPaymentTransactionTable({
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
const columns = useBillPaymentTransactionsColumns();
const { billId } = useBillDrawerContext();
// Handle fetch bill payment transaction.
const {
isLoading: isPaymentTransactionsLoading,
isFetching: isPaymentTransactionFetching,
data: paymentTransactions,
} = useBillPaymentTransactions(billId, {
enabled: !!billId,
});
// Handles delete bill payment transactions.
const handleDeleteBillPaymentTransactons = ({ bill_payment_id }) => {
openAlert('payment-made-delete', {
paymentMadeId: bill_payment_id,
});
};
// Handles edit bill payment transactions.
const handleEditBillPaymentTransactions = ({ bill_payment_id }) => {
history.push(`/payment-mades/${bill_payment_id}/edit`);
closeDrawer('bill-drawer');
};
return (
<Card>
<DataTable
columns={columns}
data={paymentTransactions}
loading={isPaymentTransactionsLoading}
headerLoading={isPaymentTransactionsLoading}
progressBarLoading={isPaymentTransactionFetching}
TableLoadingRenderer={TableSkeletonRows}
styleName={TableStyle.Constrant}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeleteBillPaymentTransactons,
onEdit: handleEditBillPaymentTransactions,
}}
/>
</Card>
);
}
export default compose(
withAlertsActions,
withDrawerActions,
)(BillPaymentTransactionTable);

View File

@@ -0,0 +1,92 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import clsx from 'classnames';
import { CLASSES } from '../../../../common/classes';
import { Can, FormatDateCell, Icon } from '../../../../components';
import { safeCallback } from 'utils';
import {
PaymentMadeAction,
AbilitySubject,
} from '../../../../common/abilityOption';
/**
* Table actions menu.
*/
export function ActionsMenu({
row: { original },
payload: { onEdit, onDelete },
}) {
return (
<Menu>
<Can I={PaymentMadeAction.Edit} a={AbilitySubject.PaymentMade}>
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('invoice_transactions.action.edit_transaction')}
onClick={safeCallback(onEdit, original)}
/>
</Can>
<Can I={PaymentMadeAction.Delete} a={AbilitySubject.PaymentMade}>
<MenuDivider />
<MenuItem
text={intl.get('invoice_transactions.action.delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Can>
</Menu>
);
}
/**
* Retrieve bill payment transactions table columns.
*/
export const useBillPaymentTransactionsColumns = () => {
return React.useMemo(
() => [
{
id: 'date',
Header: intl.get('payment_date'),
accessor: 'formatted_payment_date',
Cell: FormatDateCell,
width: 110,
className: 'date',
textOverview: true,
},
{
id: 'payment_account_name',
Header: intl.get('bill_transactions.column.deposit_account'),
accessor: 'payment_account_name',
width: 120,
textOverview: true,
},
{
id: 'amount',
Header: intl.get('amount'),
accessor: 'formatted_payment_amount',
align: 'right',
width: 100,
className: clsx(CLASSES.FONT_BOLD),
textOverview: true,
},
{
id: 'payment_number',
Header: intl.get('payment_no'),
accessor: 'payment_number',
width: 100,
className: 'payment_number',
},
{
id: 'reference',
Header: intl.get('reference_no'),
accessor: 'payment_reference_no',
width: 90,
className: 'payment_reference_no',
clickable: true,
textOverview: true,
},
],
[],
);
};

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { DataTable, Card } from 'components';
import { Button, Classes, NavbarGroup } from '@blueprintjs/core';
import { DataTable, Card, FormattedMessage as T } from 'components';
import { useLocatedLandedCostColumns, ActionsMenu } from './components';
import { useBillDrawerContext } from './BillDrawerProvider';
@@ -9,9 +8,11 @@ import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { TableStyle } from '../../../common';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import { compose } from 'utils';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import Icon from 'components/Icon';
/**
* Located landed cost table.
@@ -26,7 +27,10 @@ function LocatedLandedCostTable({
// #withDrawerActions
openDrawer,
}) {
// Located landed cost table columns.
const columns = useLocatedLandedCostColumns();
// Bill drawer context.
const { transactions, billId } = useBillDrawerContext();
// Handle the transaction delete action.
@@ -34,11 +38,6 @@ function LocatedLandedCostTable({
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;
@@ -57,27 +56,17 @@ function LocatedLandedCostTable({
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}
TableLoadingRenderer={TableSkeletonRows}
styleName={TableStyle.Constrant}
payload={{
onDelete: handleDeleteTransaction,
onFromTranscationClick: handleFromTransactionClick,
}}
className={'datatable--landed-cost-transactions'}
/>
</Card>
</div>

View File

@@ -1,8 +1,12 @@
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import clsx from 'classnames';
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
import { safeCallback } from 'utils';
import { Icon } from 'components';
import { CLASSES } from 'common/classes';
import { Icon } from 'components';
/**
* Actions menu.
*/
@@ -24,7 +28,7 @@ export function ActionsMenu({ row: { original }, payload: { onDelete } }) {
*/
export function FromTransactionCell({
row: { original },
payload: { onFromTranscationClick }
payload: { onFromTranscationClick },
}) {
// Handle the link click
const handleAnchorClick = () => {
@@ -38,6 +42,18 @@ export function FromTransactionCell({
);
}
/**
* Name accessor.
*/
export const NameAccessor = (row) => {
return (
<span className="name">
<LabelName>{row.name}</LabelName>
<LabelDescription>{row.description}</LabelDescription>
</span>
);
};
/**
* Retrieve bill located landed cost table columns.
*/
@@ -46,30 +62,41 @@ export function useLocatedLandedCostColumns() {
() => [
{
Header: intl.get('name'),
accessor: 'description',
accessor: NameAccessor,
width: 150,
className: 'name',
textOverview: true,
},
{
Header: intl.get('amount'),
accessor: 'formatted_amount',
width: 100,
className: 'amount',
align: 'right',
textOverview: true,
className: clsx(CLASSES.FONT_BOLD),
},
{
id: 'from_transaction',
Header: intl.get('From transaction'),
Cell: FromTransactionCell,
width: 100,
className: 'from-transaction',
textOverview: true,
},
{
Header: intl.get('allocation_method'),
accessor: 'allocation_method_formatted',
width: 100,
className: 'allocation-method',
textOverview: true,
},
],
[],
);
}
const LabelName = styled.div``;
const LabelDescription = styled.div`
font-size: 12px;
margin-top: 2px;
display: block;
opacity: 0.75;
`;

View File

@@ -1,51 +1,136 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormatNumberCell } from '../../../components';
import styled from 'styled-components';
import {
Button,
Popover,
PopoverInteractionKind,
Position,
MenuItem,
Menu,
Intent,
Tag,
} from '@blueprintjs/core';
import {
FormatNumberCell,
FormattedMessage as T,
Choose,
Icon,
} 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
},
],
[],
);
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,
},
],
[],
);
/**
* Bill details status.
* @returns {React.JSX}
*/
export function BillDetailsStatus({ bill }) {
return (
<Choose>
<Choose.When condition={bill.is_fully_paid && bill.is_open}>
<StatusTag intent={Intent.SUCCESS} round={true}>
<T id={'paid'} />
</StatusTag>
</Choose.When>
<Choose.When condition={bill.is_open}>
<Choose>
<Choose.When condition={bill.is_overdue}>
<StatusTag intent={Intent.WARNING} round={true}>
<T id={'overdue'} />
</StatusTag>
</Choose.When>
<Choose.Otherwise>
<StatusTag intent={Intent.PRIMARY} round={true}>
<T id={'due'} />
</StatusTag>
</Choose.Otherwise>
</Choose>
</Choose.When>
<Choose.Otherwise>
<StatusTag round={true} minimal={true}>
<T id={'draft'} />
</StatusTag>
</Choose.Otherwise>
</Choose>
);
}
export const BillMenuItem = ({
payload: { onConvert, onAllocateLandedCost },
}) => {
return (
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={
<Menu>
<MenuItem
onClick={onAllocateLandedCost}
text={<T id={'bill.allocate_landed_coast'} />}
/>
<MenuItem
onClick={onConvert}
text={<T id={'bill.convert_to_credit_note'} />}
/>
</Menu>
}
>
<Button icon={<Icon icon="more-vert" iconSize={16} />} minimal={true} />
</Popover>
);
};
const StatusTag = styled(Tag)`
min-width: 65px;
text-align: center;
`;

View File

@@ -1,9 +1,8 @@
import React from 'react';
import Icon from 'components/Icon';
import { Button, Classes, NavbarGroup, Intent } from '@blueprintjs/core';
import { Can, FormattedMessage as T } from 'components';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { Can, FormattedMessage as T, DrawerActionsBar } from 'components';
import { useCashflowTransactionDrawerContext } from './CashflowTransactionDrawerProvider';
import withAlertsActions from 'containers/Alert/withAlertActions';
import { AbilitySubject, CashflowAction } from '../../../common/abilityOption';
@@ -25,7 +24,7 @@ function CashflowTransactionDrawerActionBar({
return (
<Can I={CashflowAction.Delete} a={AbilitySubject.Cashflow}>
<DashboardActionsBar>
<DrawerActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
@@ -35,8 +34,9 @@ function CashflowTransactionDrawerActionBar({
onClick={handleDeleteCashflowTransaction}
/>
</NavbarGroup>
</DashboardActionsBar>
</DrawerActionsBar>
</Can>
);
}
export default compose(withAlertsActions)(CashflowTransactionDrawerActionBar);

View File

@@ -13,6 +13,7 @@ export default function CashflowTransactionDrawerDetails() {
return (
<div className={'cashflow-drawer'}>
<CashflowTransactionDrawerActionBar />
<div className="cashflow-drawer__content">
<Card>
<CashflowTransactionDrawerHeader />

View File

@@ -5,6 +5,9 @@ import {
DetailItem,
FormatDate,
FormattedMessage as T,
Row,
Col,
CommercialDocHeader,
} from 'components';
import { useCashflowTransactionDrawerContext } from './CashflowTransactionDrawerProvider';
@@ -12,52 +15,45 @@ import { useCashflowTransactionDrawerContext } from './CashflowTransactionDrawer
* Cashlflow transaction drawer detail Header.
*/
export default function CashflowTransactionDrawerHeader() {
const {
cashflowTransaction: {
formatted_amount,
transaction_type,
transaction_number,
reference_no,
date,
description,
},
} = useCashflowTransactionDrawerContext();
const { cashflowTransaction } = useCashflowTransactionDrawerContext();
return (
<div className={'cashflow-drawer__content-header'}>
<DetailsMenu>
<DetailItem name={'total'} label={<T id={'total'} />}>
<h3 class="amount">{formatted_amount}</h3>
</DetailItem>
<CommercialDocHeader>
<CommercialDocHeader>
<DetailsMenu>
<DetailItem name={'total'} label={<T id={'total'} />}>
<h3 class="big-number">{cashflowTransaction.formatted_amount}</h3>
</DetailItem>
</DetailsMenu>
</CommercialDocHeader>
<DetailItem
name={'transaction_type'}
label={<T id={'cash_flow_drawer.label_transaction_type'} />}
>
{transaction_type}
</DetailItem>
<Row>
<Col xs={6}>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem
name={'transaction_type'}
label={<T id={'cash_flow_drawer.label_transaction_type'} />}
>
{cashflowTransaction.transaction_type_formatted}
</DetailItem>
<DetailItem
name={'transaction_number'}
label={<T id={'cash_flow.drawer.label_transaction_no'} />}
>
{transaction_number}
</DetailItem>
<DetailItem
label={<T id={'date'} />}
children={<FormatDate value={date} />}
/>
<DetailItem name={'reference-no'} label={<T id={'reference_no'} />}>
{defaultTo(reference_no, '-')}
</DetailItem>
</DetailsMenu>
<DetailItem
name={'transaction_number'}
label={<T id={'cash_flow.drawer.label_transaction_no'} />}
>
{cashflowTransaction.transaction_number}
</DetailItem>
<div class="cashflow-drawer__content-description">
<b class="title">
<T id={'description'} />
</b>
: {defaultTo(description, '—')}
</div>
</div>
<DetailItem label={<T id={'date'} />}>
<FormatDate value={cashflowTransaction.date} />
</DetailItem>
<DetailItem name={'reference-no'} label={<T id={'reference_no'} />}>
{defaultTo(cashflowTransaction.reference_no, '-')}
</DetailItem>
</DetailsMenu>
</Col>
</Row>
</CommercialDocHeader>
);
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { DataTable, If, FormattedMessage as T } from 'components';
import { CommercialDocEntriesTable } from 'components';
import { useCashflowTransactionColumns } from './utils';
import { useCashflowTransactionDrawerContext } from './CashflowTransactionDrawerProvider';
@@ -14,9 +14,5 @@ export default function CashflowTransactionDrawerTable() {
cashflowTransaction: { transactions },
} = useCashflowTransactionDrawerContext();
return (
<div className="cashflow-drawer__content-table">
<DataTable columns={columns} data={transactions} />
</div>
);
return <CommercialDocEntriesTable columns={columns} data={transactions} />;
}

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { Tab } from '@blueprintjs/core';
import styled from 'styled-components';
import intl from 'react-intl-universal';
import { useAbilityContext } from 'hooks/utils';
import { DrawerMainTabs } from 'components';
import CreditNoteDetailActionsBar from './CreditNoteDetailActionsBar';
import CreditNoteDetailPanel from './CreditNoteDetailPanel';
import RefundCreditNoteTransactionsTable from './RefundCreditNoteTransactions/RefundCreditNoteTransactionsTable';
import ReconcileCreditNoteTransactionsTable from './ReconcileCreditNoteTransactions/ReconcileCreditNoteTransactionsTable';
import { CreditNoteGLEntriesTable } from './JournalEntriesTransactions/JournalEntriesTransactionsTable';
import {
CreditNoteAction,
AbilitySubject,
} from '../../../common/abilityOption';
/**
* Credit Note view detail.
* @returns {React.JSX}
*/
export default function CreditNoteDetail() {
return (
<CreditNoteRoot>
<CreditNoteDetailActionsBar />
<CreditNoteDetailsTabs />
</CreditNoteRoot>
);
}
/**
* Credit note details tabs.
* @returns {React.JSX}
*/
function CreditNoteDetailsTabs() {
const ability = useAbilityContext();
return (
<DrawerMainTabs>
<Tab
title={intl.get('details')}
id={'details'}
panel={<CreditNoteDetailPanel />}
/>
<Tab
title={intl.get('journal_entries')}
id={'journal_entries'}
panel={<CreditNoteGLEntriesTable />}
/>
{ability.can(CreditNoteAction.View, AbilitySubject.CreditNote) && (
<Tab
title={intl.get('credit_note.drawer.label_refund_transactions')}
id={'refund_transactions'}
panel={<RefundCreditNoteTransactionsTable />}
/>
)}
{ability.can(CreditNoteAction.View, AbilitySubject.CreditNote) && (
<Tab
title={intl.get('credit_note.drawer.label_invoices_reconciled')}
id={'reconcile_transactions'}
panel={<ReconcileCreditNoteTransactionsTable />}
/>
)}
</DrawerMainTabs>
);
}
const CreditNoteRoot = styled.div``;

View File

@@ -0,0 +1,119 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import {
DrawerActionsBar,
Can,
Icon,
FormattedMessage as T,
If,
} from 'components';
import {
CreditNoteAction,
AbilitySubject,
} from '../../../common/abilityOption';
import { compose } from 'utils';
import { CreditNoteMenuItem } from './utils';
/**
* Credit note detail actions bar.
*/
function CreditNoteDetailActionsBar({
// #withDialogActions
openDialog,
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const { creditNoteId, creditNote } = useCreditNoteDetailDrawerContext();
const history = useHistory();
// Handle edit credit note.
const handleEditCreditNote = () => {
history.push(`/credit-notes/${creditNoteId}/edit`);
closeDrawer('credit-note-detail-drawer');
};
const handleRefundCreditNote = () => {
openDialog('refund-credit-note', { creditNoteId });
};
// Handle delete credit note.
const handleDeleteCreditNote = () => {
openAlert('credit-note-delete', { creditNoteId });
};
const handleReconcileCreditNote = () => {
openDialog('reconcile-credit-note', { creditNoteId });
};
return (
<DrawerActionsBar>
<NavbarGroup>
<Can I={CreditNoteAction.Edit} a={AbilitySubject.CreditNote}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'credit_note.action.edit_credit_note'} />}
onClick={handleEditCreditNote}
/>
<NavbarDivider />
</Can>
<Can I={CreditNoteAction.Refund} a={AbilitySubject.CreditNote}>
<If condition={!creditNote.is_closed && !creditNote.is_draft}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="arrow-upward" iconSize={18} />}
text={<T id={'refund'} />}
onClick={handleRefundCreditNote}
/>
<NavbarDivider />
</If>
</Can>
<Can I={CreditNoteAction.Delete} a={AbilitySubject.CreditNote}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeleteCreditNote}
/>
</Can>
<Can I={CreditNoteAction.Edit} a={AbilitySubject.CreditNote}>
<If condition={creditNote.is_published && !creditNote.is_closed}>
<NavbarDivider />
<CreditNoteMenuItem
payload={{
onReconcile: handleReconcileCreditNote,
}}
/>
</If>
</Can>
</NavbarGroup>
</DrawerActionsBar>
);
}
export default compose(
withDialogActions,
withAlertsActions,
withDrawerActions,
)(CreditNoteDetailActionsBar);

View File

@@ -0,0 +1,21 @@
import React from 'react';
import { DrawerBody } from 'components';
import CreditNoteDetail from './CreditNoteDetail';
import { CreditNoteDetailDrawerProvider } from './CreditNoteDetailDrawerProvider';
/**
* Credit note detail drawer content.
*/
export default function CreditNoteDetailDrawerContent({
// #ownProp
creditNoteId,
}) {
return (
<CreditNoteDetailDrawerProvider creditNoteId={creditNoteId}>
<DrawerBody>
<CreditNoteDetail />
</DrawerBody>
</CreditNoteDetailDrawerProvider>
);
}

View File

@@ -0,0 +1,82 @@
import React from 'react';
import intl from 'react-intl-universal';
import {
useCreditNote,
useRefundCreditNote,
useReconcileCreditNote,
useReconcileCreditNotes,
} from 'hooks/query';
import { DrawerHeaderContent, DrawerLoading } from 'components';
const CreditNoteDetailDrawerContext = React.createContext();
/**
* Credit note detail drawer provider.
*/
function CreditNoteDetailDrawerProvider({ creditNoteId, ...props }) {
// Handle fetch vendor credit details.
const { data: creditNote, isLoading: isCreditNoteLoading } = useCreditNote(
creditNoteId,
{
enabled: !!creditNoteId,
},
);
// Handle fetch refund credit note.
const {
data: refundCreditNote,
isFetching: isRefundCreditNoteFetching,
isLoading: isRefundCreditNoteLoading,
} = useRefundCreditNote(creditNoteId, {
enabled: !!creditNoteId,
});
// Handle fetch refund credit note.
const {
data: reconcileCreditNotes,
isLoading: isReconcileCreditNoteLoading,
} = useReconcileCreditNotes(creditNoteId, {
enabled: !!creditNoteId,
});
// Handle fetch reconcile credit note details.
const { isLoading: isReconcileCreditLoading, data: reconcileCreditNote } =
useReconcileCreditNote(creditNoteId, {
enabled: !!creditNoteId,
});
const provider = {
creditNote,
refundCreditNote,
reconcileCreditNote,
reconcileCreditNotes,
isRefundCreditNoteLoading,
isRefundCreditNoteFetching,
creditNoteId,
};
return (
<DrawerLoading
loading={
isCreditNoteLoading ||
isRefundCreditNoteLoading ||
isReconcileCreditNoteLoading ||
isReconcileCreditLoading
}
>
<DrawerHeaderContent
name="credit-note-detail-drawer"
title={intl.get('credit_note.drawer.title', {
number: creditNote.credit_note_number,
})}
/>
<CreditNoteDetailDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useCreditNoteDetailDrawerContext = () =>
React.useContext(CreditNoteDetailDrawerContext);
export { CreditNoteDetailDrawerProvider, useCreditNoteDetailDrawerContext };

View File

@@ -0,0 +1,29 @@
import React from 'react';
import {
CommercialDocFooter,
T,
If,
DetailsMenu,
DetailItem,
} from 'components';
import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvider';
/**
* Credit note detail footer
* @returns {React.JSX}
*/
export default function CreditNoteDetailFooter() {
const { creditNote } = useCreditNoteDetailDrawerContext();
return (
<CommercialDocFooter>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<If condition={creditNote.terms_conditions}>
<DetailItem label={<T id={'terms_conditions'} />}>
{creditNote.terms_conditions}
</DetailItem>
</If>
</DetailsMenu>
</CommercialDocFooter>
);
}

View File

@@ -0,0 +1,103 @@
import React from 'react';
import intl from 'react-intl-universal';
import { defaultTo } from 'lodash';
import styled from 'styled-components';
import {
FormatDate,
T,
Row,
Col,
DetailsMenu,
DetailItem,
ButtonLink,
CommercialDocHeader,
CommercialDocTopHeader,
CustomerDrawerLink,
} from 'components';
import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvider';
import { CreditNoteDetailsStatus } from './utils';
/**
* Credit note details drawer header.
*/
export default function CreditNoteDetailHeader() {
const { creditNote } = useCreditNoteDetailDrawerContext();
return (
<CommercialDocHeader>
<CommercialDocTopHeader>
<DetailsMenu>
<AmountItem label={intl.get('amount')}>
<span class="big-number">{creditNote.formatted_amount}</span>
</AmountItem>
<StatusItem>
<CreditNoteDetailsStatus creditNote={creditNote} />
</StatusItem>
</DetailsMenu>
</CommercialDocTopHeader>
<Row>
<Col xs={6}>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem
label={intl.get('credit_note.drawer.label_credit_note_no')}
>
{defaultTo(creditNote.credit_note_number, '-')}
</DetailItem>
<DetailItem label={intl.get('customer_name')}>
<CustomerDrawerLink customerId={creditNote.customer_id}>
{creditNote.customer?.display_name}
</CustomerDrawerLink>
</DetailItem>
<DetailItem
label={intl.get('credit_note.drawer.label_credit_note_date')}
>
<FormatDate value={creditNote.formatted_credit_note_date} />
</DetailItem>
</DetailsMenu>
</Col>
<Col xs={6}>
<DetailsMenu
textAlign={'right'}
direction={'horizantal'}
minLabelSize={'180px'}
>
<DetailItem
label={intl.get('credit_note.drawer.label_credits_remaining')}
>
<strong>{creditNote.formatted_credits_remaining}</strong>
</DetailItem>
<DetailItem
label={intl.get('reference')}
children={defaultTo(creditNote.reference_no, '-')}
/>
<DetailItem
label={intl.get('note')}
children={defaultTo(creditNote.note, '-')}
/>
<DetailItem
label={<T id={'credit_note.drawer.label_created_at'} />}
children={<FormatDate value={creditNote.created_at} />}
/>
</DetailsMenu>
</Col>
</Row>
</CommercialDocHeader>
);
}
const StatusItem = styled(DetailItem)`
width: 50%;
text-align: right;
`;
const AmountItem = styled(DetailItem)`
width: 50%;
`;

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { CommercialDocBox } from 'components';
import CreditNoteDetailHeader from './CreditNoteDetailHeader';
import CreditNoteDetailTable from './CreditNoteDetailTable';
import CreditNoteDetailTableFooter from './CreditNoteDetailTableFooter';
import CreditNoteDetailFooter from './CreditNoteDetailFooter';
/**
* Credit note details panel.
*/
export default function CreditNoteDetailPanel() {
return (
<CommercialDocBox>
<CreditNoteDetailHeader />
<CreditNoteDetailTable />
<CreditNoteDetailTableFooter />
<CreditNoteDetailFooter />
</CommercialDocBox>
);
}

View File

@@ -0,0 +1,27 @@
import React from 'react';
import { CommercialDocEntriesTable } from 'components';
import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvider';
import { useCreditNoteReadOnlyEntriesColumns } from './utils';
/**
* Credit note detail table.
* @returns {React.JSX}
*/
export default function CreditNoteDetailTable() {
const {
creditNote: { entries },
} = useCreditNoteDetailDrawerContext();
// Credit note entries table columns.
const columns = useCreditNoteReadOnlyEntriesColumns();
return (
<CommercialDocEntriesTable
columns={columns}
data={entries}
className={'table-constrant'}
/>
);
}

View File

@@ -0,0 +1,42 @@
import React from 'react';
import styled from 'styled-components';
import {
T,
TotalLines,
TotalLine,
FormatNumber,
TotalLineBorderStyle,
TotalLineTextStyle,
} from 'components';
import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvider';
/**
* Credit note details panel footer.
*/
export default function CreditNoteDetailTableFooter() {
const { creditNote } = useCreditNoteDetailDrawerContext();
return (
<CreditNoteDetailsFooterRoot>
<CreditNoteTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine
title={<T id={'credit_note.drawer.label_subtotal'} />}
value={<FormatNumber value={creditNote.formatted_amount} />}
/>
<TotalLine
title={<T id={'credit_note.drawer.label_total'} />}
value={creditNote.formatted_amount}
borderStyle={TotalLineBorderStyle.DoubleDark}
textStyle={TotalLineTextStyle.Bold}
/>
</CreditNoteTotalLines>
</CreditNoteDetailsFooterRoot>
);
}
export const CreditNoteDetailsFooterRoot = styled.div``;
export const CreditNoteTotalLines = styled(TotalLines)`
margin-left: auto;
`;

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { Card } from 'components';
import { useCreditNoteDetailDrawerContext } from '../CreditNoteDetailDrawerProvider';
import { useTransactionsByReference } from 'hooks/query';
import { useJournalEntriesTransactionsColumns } from './components';
import JournalEntriesTable, {
AmountDisplayedBaseCurrencyMessage,
} from '../../../JournalEntriesTable/JournalEntriesTable';
/**
* Journal entries table.
*/
export function CreditNoteGLEntriesTable() {
const { creditNoteId } = useCreditNoteDetailDrawerContext();
// Credit note GL entries table columns.
const columns = useJournalEntriesTransactionsColumns();
// Handle fetch transaction by reference.
const {
data: { transactions },
isLoading: isTransactionLoading,
} = useTransactionsByReference(
{
reference_id: creditNoteId,
reference_type: 'creditNote',
},
{ enabled: !!creditNoteId },
);
return (
<Card>
<AmountDisplayedBaseCurrencyMessage />
<JournalEntriesTable
columns={columns}
data={transactions}
loading={isTransactionLoading}
/>
</Card>
);
}

View File

@@ -0,0 +1,49 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormatDateCell, Icon } from '../../../../components';
import 'style/pages/JournalEntries/List.scss';
/**
* Retrieve journal entries transactions table columns.
*/
export const useJournalEntriesTransactionsColumns = () => {
return React.useMemo(
() => [
{
Header: intl.get('date'),
accessor: 'date',
accessor: 'formatted_date',
Cell: FormatDateCell,
width: 140,
className: 'date',
textOverview: true,
},
{
Header: intl.get('account_name'),
accessor: 'account_name',
width: 140,
className: 'account_name',
textOverview: true,
},
{
Header: intl.get('contact'),
accessor: 'contactTypeFormatted',
width: 140,
},
{
Header: intl.get('credit'),
accessor: ({ credit }) => credit.formatted_amount,
width: 100,
className: 'credit',
},
{
Header: intl.get('debit'),
accessor: ({ debit }) => debit.formatted_amount,
width: 100,
className: 'debit',
},
],
[],
);
};

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { DataTable, Card } from 'components';
import { TableStyle } from 'common';
import withAlertsActions from 'containers/Alert/withAlertActions';
import { useCreditNoteDetailDrawerContext } from '../CreditNoteDetailDrawerProvider';
import {
useReconcileCreditTransactionsTableColumns,
ActionsMenu,
} from './components';
import { compose } from 'utils';
/**
* Reconcile credit transactions table.
*/
function RefundCreditNoteTransactionsTable({
// #withAlertsActions
openAlert,
}) {
// Credit note drawer context.
const { reconcileCreditNotes } = useCreditNoteDetailDrawerContext();
// Reconcile credit transactions table columns.
const columns = useReconcileCreditTransactionsTableColumns();
// Handle delete reconile credit.
const handleDeleteReconcileCreditNote = ({ id }) => {
openAlert('reconcile-credit-delete', { creditNoteId: id });
};
return (
<Card>
<DataTable
columns={columns}
data={reconcileCreditNotes}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeleteReconcileCreditNote,
}}
styleName={TableStyle.Constrant}
className={'datatable--refund-transactions'}
/>
</Card>
);
}
export default compose(withAlertsActions)(RefundCreditNoteTransactionsTable);

View File

@@ -0,0 +1,58 @@
import React from 'react';
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import { Can, FormatDateCell, Icon } from 'components';
import { safeCallback } from 'utils';
import {
CreditNoteAction,
AbilitySubject,
} from '../../../../common/abilityOption';
/**
* Actions menu.
*/
export function ActionsMenu({ payload: { onDelete }, row: { original } }) {
return (
<Menu>
<Can I={CreditNoteAction.Delete} a={AbilitySubject.CreditNote}>
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
/>
</Can>
</Menu>
);
}
/**
* Credit note reconcilation with invoices table columns.
*/
export function useReconcileCreditTransactionsTableColumns() {
return React.useMemo(
() => [
{
Header: intl.get('date'),
accessor: 'formatted_credit_note_date',
Cell: FormatDateCell,
width: 100,
className: 'date',
},
{
Header: intl.get('invoice_no'),
accessor: 'invoice_number',
width: 100,
className: 'invoice_number',
},
{
Header: intl.get('amount'),
accessor: 'formtted_amount',
width: 100,
className: 'amount',
align: 'right',
},
],
[],
);
}

View File

@@ -0,0 +1,47 @@
import React from 'react';
import { DataTable, Card } from 'components';
import { TableStyle } from 'common';
import withAlertsActions from 'containers/Alert/withAlertActions';
import { useCreditNoteDetailDrawerContext } from '../CreditNoteDetailDrawerProvider';
import {
useRefundCreditTransactionsTableColumns,
ActionsMenu,
} from './components';
import { compose } from 'utils';
/**
* Refund credit note transactions table.
*/
function RefundCreditNoteTransactionsTable({
// #withAlertsActions
openAlert,
}) {
const { refundCreditNote } = useCreditNoteDetailDrawerContext();
// Refund credit transactions table columns.
const columns = useRefundCreditTransactionsTableColumns();
// Handle delete refund credit.
const handleDeleteRefundCreditNote = ({ id }) => {
openAlert('refund-credit-delete', { creditNoteId: id });
};
return (
<Card>
<DataTable
columns={columns}
data={refundCreditNote}
ContextMenu={ActionsMenu}
styleName={TableStyle.Constrant}
payload={{
onDelete: handleDeleteRefundCreditNote,
}}
/>
</Card>
);
}
export default compose(withAlertsActions)(RefundCreditNoteTransactionsTable);

View File

@@ -0,0 +1,66 @@
import React from 'react';
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import { Can, FormatDateCell, Icon } from 'components';
import { safeCallback } from 'utils';
import {
CreditNoteAction,
AbilitySubject,
} from '../../../../common/abilityOption';
/**
* Actions menu.
*/
export function ActionsMenu({ payload: { onDelete }, row: { original } }) {
return (
<Menu>
<Can I={CreditNoteAction.Delete} a={AbilitySubject.CreditNote}>
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
/>
</Can>
</Menu>
);
}
export function useRefundCreditTransactionsTableColumns() {
return React.useMemo(
() => [
{
Header: intl.get('date'),
accessor: 'formatted_date',
Cell: FormatDateCell,
width: 100,
className: 'date',
},
{
Header: intl.get('refund_credit_transactions.column.amount_refunded'),
accessor: 'formtted_amount',
width: 100,
className: 'amount',
align: 'right',
},
{
id: 'from_account',
Header: intl.get(
'refund_credit_transactions.column.withdrawal_account',
),
accessor: ({ from_account }) => from_account.name,
width: 100,
className: 'from_account',
},
{
id: 'reference_no',
Header: intl.get('reference_no'),
accessor: 'reference_no',
width: 100,
className: 'reference_no',
textOverview: true,
},
],
[],
);
}

View 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 CreditNoteDetailDrawerContent = React.lazy(() =>
import('./CreditNoteDetailDrawerContent'),
);
/**
* Credit note detail drawer.
*/
function CreditNoteDetailDrawer({
name,
// #withDrawer
isOpen,
payload: { creditNoteId },
}) {
return (
<Drawer
isOpen={isOpen}
name={name}
style={{ minWidth: '700px', maxWidth: '900px' }}
size={'65%'}
>
<DrawerSuspense>
<CreditNoteDetailDrawerContent creditNoteId={creditNoteId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers())(CreditNoteDetailDrawer);

View File

@@ -0,0 +1,117 @@
import React from 'react';
import intl from 'react-intl-universal';
import {
Button,
Popover,
PopoverInteractionKind,
Position,
MenuItem,
Menu,
Tag,
Intent,
} from '@blueprintjs/core';
import {
Icon,
FormattedMessage as T,
FormatNumberCell,
Choose,
} from '../../../components';
export const useCreditNoteReadOnlyEntriesColumns = () =>
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,
},
],
[],
);
/**
* Credit note more actions mneu.
* @returns {React.JSX}
*/
export function CreditNoteMenuItem({ payload: { onReconcile } }) {
return (
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={
<Menu>
<MenuItem
onClick={onReconcile}
text={<T id={'credit_note.action.reconcile_with_invoices'} />}
/>
</Menu>
}
>
<Button icon={<Icon icon="more-vert" iconSize={16} />} minimal={true} />
</Popover>
);
}
/**
* Credit note details status.
* @returns {React.JSX}
*/
export function CreditNoteDetailsStatus({ creditNote }) {
return (
<Choose>
<Choose.When condition={creditNote.is_open}>
<Tag intent={Intent.WARNING} round={true}>
<T id={'open'} />
</Tag>
</Choose.When>
<Choose.When condition={creditNote.is_closed}>
<Tag intent={Intent.SUCCESS} round={true}>
<T id={'closed'} />
</Tag>
</Choose.When>
<Choose.When condition={creditNote.is_draft}>
<Tag intent={Intent.NONE} round={true} minimal={true}>
<T id={'draft'} />
</Tag>
</Choose.When>
</Choose>
);
}

View File

@@ -28,15 +28,10 @@ export default function CustomerDetailsHeader() {
<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}
children={customer?.formatted_customer_type }
/>
<DetailItem label={<T id={'customer.drawer.label.unused_credits'} />}>
0
@@ -44,6 +39,13 @@ export default function CustomerDetailsHeader() {
</DetailsMenu>
<DetailsMenu direction={'horizantal'} minLabelSize={'175px'}>
<DetailItem
label={<T id={'customer.drawer.label.customer_name'} />}
name={'name'}
>
<strong>{customer?.display_name}</strong>
</DetailItem>
<DetailItem
label={<T id={'customer.drawer.label.company_name'} />}
children={defaultTo(customer?.company_name, '--')}

View File

@@ -1,26 +1,39 @@
import React from 'react';
import { Tab } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { DrawerMainTabs } from 'components';
import EstimateDetailActionsBar from './EstimateDetailActionsBar';
import EstimateDetailPanel from './EstimateDetailPanel';
import clsx from 'classnames';
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
/**
* Estimate details tabs.
* @returns {React.JSX}
*/
function EstimateDetailsTabs() {
return (
<DrawerMainTabs>
<Tab
title={intl.get('details')}
id={'details'}
panel={<EstimateDetailPanel />}
/>
</DrawerMainTabs>
);
}
/**
* 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>
<EstimateDetailsRoot>
<EstimateDetailActionsBar />
<EstimateDetailsTabs />
</EstimateDetailsRoot>
);
}
const EstimateDetailsRoot = styled.div``;

View File

@@ -8,7 +8,7 @@ import {
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -19,7 +19,13 @@ import {
AbilitySubject,
} from '../../../common/abilityOption';
import { Icon, FormattedMessage as T, MoreMenuItems, Can } from 'components';
import {
DrawerActionsBar,
Icon,
FormattedMessage as T,
MoreMenuItems,
Can,
} from 'components';
import { compose } from 'utils';
@@ -36,8 +42,10 @@ function EstimateDetailActionsBar({
// #withDrawerActions
closeDrawer,
}) {
// Estimate details drawer context.
const { estimateId } = useEstimateDetailDrawerContext();
// History.
const history = useHistory();
// Handle edit sale estimate.
@@ -61,7 +69,7 @@ function EstimateDetailActionsBar({
};
return (
<DashboardActionsBar>
<DrawerActionsBar>
<NavbarGroup>
<Can I={SaleEstimateAction.Edit} a={AbilitySubject.Estimate}>
<Button
@@ -90,13 +98,15 @@ function EstimateDetailActionsBar({
/>
<NavbarDivider />
</Can>
<MoreMenuItems
payload={{
onNotifyViaSMS: handleNotifyViaSMS,
}}
/>
<Can I={SaleEstimateAction.NotifyBySms} a={AbilitySubject.Estimate}>
<MoreMenuItems
payload={{
onNotifyViaSMS: handleNotifyViaSMS,
}}
/>
</Can>
</NavbarGroup>
</DashboardActionsBar>
</DrawerActionsBar>
);
}

View File

@@ -24,7 +24,9 @@ function EstimateDetailDrawerProvider({ estimateId, ...props }) {
<DrawerLoading loading={isEstimateLoading}>
<DrawerHeaderContent
name="estimate-detail-drawer"
title={intl.get('estimate_details')}
title={intl.get('estimate.drawer.title', {
number: estimate.estimate_number,
})}
/>
<EstimateDetailDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>

View File

@@ -1,40 +1,35 @@
import React from 'react';
import clsx from 'classnames';
import { T, TotalLines, TotalLine, If } from 'components';
import {
CommercialDocFooter,
T,
If,
DetailsMenu,
DetailItem,
} from 'components';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
import { FormatNumber } from '../../../components';
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
/**
* Estimate details panel footer content.
* Estimate details footer.
* @returns {React.JSX}
*/
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>
<CommercialDocFooter>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<If condition={estimate.terms_conditions}>
<DetailItem label={<T id={'estimate.details.terms_conditions'} />}>
{estimate.terms_conditions}
</DetailItem>
</If>
<If condition={estimate.note}>
<DetailItem label={<T id={'estimate.details.note'} />}>
{estimate.note}
</DetailItem>
</If>
</DetailsMenu>
</CommercialDocFooter>
);
}

View File

@@ -1,12 +1,22 @@
import React from 'react';
import intl from 'react-intl-universal';
import { defaultTo } from 'lodash';
import clsx from 'classnames';
import styled from 'styled-components';
import { FormatDate, T, DetailsMenu, DetailItem } from 'components';
import {
CommercialDocHeader,
CommercialDocTopHeader,
FormatDate,
T,
DetailsMenu,
DetailItem,
Row,
Col,
ButtonLink,
CustomerDrawerLink,
} from 'components';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
import { EstimateDetailsStatus } from './components';
/**
* Estimate read-only details drawer header.
@@ -15,39 +25,70 @@ 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>
<CommercialDocHeader>
<CommercialDocTopHeader>
<DetailsMenu>
<AmountEstimateDetail label={intl.get('amount')}>
<span class="big-number">{estimate.formatted_amount}</span>
</AmountEstimateDetail>
<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>
<EstimateStatusDetail>
<EstimateDetailsStatus estimate={estimate} />
</EstimateStatusDetail>
</DetailsMenu>
</CommercialDocTopHeader>
<Row>
<Col xs={6}>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem
label={intl.get('estimate.details.estimate_number')}
children={defaultTo(estimate.estimate_number, '-')}
/>
<DetailItem label={intl.get('customer_name')}>
<CustomerDrawerLink customerId={estimate.customer_id}>
{estimate.customer?.display_name}
</CustomerDrawerLink>
</DetailItem>
<DetailItem
label={intl.get('estimate_date')}
children={estimate.formatted_estimate_date}
/>
<DetailItem
label={intl.get('expiration_date')}
children={estimate.formatted_expiration_date}
/>
</DetailsMenu>
</Col>
<Col xs={6}>
<DetailsMenu
textAlign={'right'}
direction={'horizantal'}
minLabelSize={'180px'}
>
<DetailItem
label={intl.get('reference')}
children={defaultTo(estimate.reference, '-')}
/>
<DetailItem
label={<T id={'estimate.details.created_at'} />}
children={<FormatDate value={estimate.created_at} />}
/>
</DetailsMenu>
</Col>
</Row>
</CommercialDocHeader>
);
}
const EstimateStatusDetail = styled(DetailItem)`
width: 50%;
text-align: right;
`;
const AmountEstimateDetail = styled(DetailItem)`
width: 50%;
`;

View File

@@ -1,25 +1,20 @@
import React from 'react';
import clsx from 'classnames';
import { Card } from 'components';
import { CommercialDocBox } from 'components';
import EstimateDetailActionsBar from './EstimateDetailActionsBar';
import EstimateDetailHeader from './EstimateDetailHeader';
import EstimateDetailTable from './EstimateDetailTable';
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
import EstimateDetailTableFooter from './EstimateDetailTableFooter';
import EstimateDetailFooter from './EstimateDetailFooter';
export default function EstimateDetailTab() {
return (
<div className={clsx(EstimateDetailsCls.detail_panel)}>
<EstimateDetailActionsBar />
<Card>
<EstimateDetailHeader />
<EstimateDetailTable />
<EstimateDetailFooter />
</Card>
</div>
<CommercialDocBox>
<EstimateDetailHeader />
<EstimateDetailTable />
<EstimateDetailTableFooter />
<EstimateDetailFooter />
</CommercialDocBox>
);
}

View File

@@ -1,12 +1,11 @@
import React from 'react';
import clsx from 'classnames';
import { DataTable } from 'components';
import { CommercialDocEntriesTable } from 'components';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
import { useEstimateReadonlyEntriesColumns } from './utils';
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
import { TableStyle } from '../../../common';
/**
* Estimate detail table.
@@ -20,12 +19,10 @@ export default function EstimateDetailTable() {
const columns = useEstimateReadonlyEntriesColumns();
return (
<div className={clsx(EstimateDetailsCls.detail_panel_table)}>
<DataTable
columns={columns}
data={entries}
className={'table-constrant'}
/>
</div>
<CommercialDocEntriesTable
columns={columns}
data={entries}
styleName={TableStyle.Constrant}
/>
);
}

View File

@@ -0,0 +1,43 @@
import React from 'react';
import styled from 'styled-components';
import {
T,
TotalLines,
TotalLine,
TotalLineBorderStyle,
TotalLineTextStyle,
FormatNumber,
} from 'components';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
/**
* Estimate details panel footer content.
*/
export default function EstimateDetailTableFooter() {
const { estimate } = useEstimateDetailDrawerContext();
return (
<EstimateDetailsFooterRoot>
<EstimateTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine
title={<T id={'estimate.details.subtotal'} />}
value={<FormatNumber value={estimate.amount} />}
borderStyle={TotalLineBorderStyle.SingleDark}
/>
<TotalLine
title={<T id={'estimate.details.total'} />}
value={estimate.formatted_amount}
borderStyle={TotalLineBorderStyle.DoubleDark}
textStyle={TotalLineTextStyle.Bold}
/>
</EstimateTotalLines>
</EstimateDetailsFooterRoot>
);
}
export const EstimateDetailsFooterRoot = styled.div``;
export const EstimateTotalLines = styled(TotalLines)`
margin-left: auto;
`;

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { Intent, Tag } from '@blueprintjs/core';
import { T, Choose } from 'components';
/**
* Estimate details status.
* @return {React.JSX}
*/
export function EstimateDetailsStatus({ estimate }) {
return (
<Choose>
<Choose.When condition={estimate.is_approved}>
<Tag intent={Intent.SUCCESS} round={true}>
<T id={'approved'} />
</Tag>
</Choose.When>
<Choose.When condition={estimate.is_rejected}>
<Tag intent={Intent.DANGER} round={true}>
<T id={'rejected'} />
</Tag>
</Choose.When>
<Choose.When condition={estimate.is_expired}>
<Tag intent={Intent.WARNING} round={true}>
<T id={'estimate.status.expired'} />
</Tag>
</Choose.When>
<Choose.When condition={estimate.is_delivered}>
<Tag intent={Intent.SUCCESS} round={true}>
<T id={'delivered'} />
</Tag>
</Choose.When>
<Choose.Otherwise>
<Tag round={true} minimal={true}>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>
</Choose>
);
}

View File

@@ -8,15 +8,17 @@ import {
Intent,
NavbarDivider,
} from '@blueprintjs/core';
import { Can, FormattedMessage as T } from 'components';
import { DrawerActionsBar, Can, FormattedMessage as T } from 'components';
import { ExpenseAction, AbilitySubject } from '../../../common/abilityOption';
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';
import { compose } from 'utils';
/**
* Expense drawer action bar.
*/
@@ -28,6 +30,8 @@ function ExpenseDrawerActionBar({
closeDrawer,
}) {
const history = useHistory();
// Expense drawer context.
const { expense } = useExpenseDrawerContext();
// Handle the expense edit action.
@@ -42,7 +46,7 @@ function ExpenseDrawerActionBar({
};
return (
<DashboardActionsBar>
<DrawerActionsBar>
<NavbarGroup>
<Can I={ExpenseAction.Edit} a={AbilitySubject.Expense}>
<Button
@@ -63,7 +67,7 @@ function ExpenseDrawerActionBar({
/>
</Can>
</NavbarGroup>
</DashboardActionsBar>
</DrawerActionsBar>
);
}

View File

@@ -2,8 +2,6 @@ import React from 'react';
import { DrawerBody } from 'components';
import 'style/components/Drawers/ExpenseDrawer.scss';
import { ExpenseDrawerProvider } from './ExpenseDrawerProvider';
import ExpenseDrawerDetails from './ExpenseDrawerDetails';

View File

@@ -1,6 +1,7 @@
import React from 'react';
import styled from 'styled-components';
import { Card } from 'components';
import { CommercialDocBox } from '../../../components';
import ExpenseDrawerActionBar from './ExpenseDrawerActionBar';
import ExpenseDrawerHeader from './ExpenseDrawerHeader';
@@ -12,16 +13,16 @@ import ExpenseDrawerFooter from './ExpenseDrawerFooter';
*/
export default function ExpenseDrawerDetails() {
return (
<div className={'expense-drawer'}>
<ExpenseDetailsRoot>
<ExpenseDrawerActionBar />
<div className="expense-drawer__content">
<Card>
<ExpenseDrawerHeader />
<ExpenseDrawerTable />
<ExpenseDrawerFooter />
</Card>
</div>
</div>
<CommercialDocBox>
<ExpenseDrawerHeader />
<ExpenseDrawerTable />
<ExpenseDrawerFooter />
</CommercialDocBox>
</ExpenseDetailsRoot>
);
}
const ExpenseDetailsRoot = styled.div``;

View File

@@ -1,8 +1,14 @@
import React from 'react';
import { T } from 'components';
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
import styled from 'styled-components';
import { FormatNumber } from '../../../components';
import {
T,
TotalLines,
TotalLineBorderStyle,
TotalLineTextStyle,
} from 'components';
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
import { FormatNumber, TotalLine } from '../../../components';
/**
* Footer details of expense readonly details.
@@ -11,23 +17,26 @@ 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>
<ExpenseDetailsFooterRoot>
<ExpenseTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine
title={<T id={'expense.details.subtotal'} />}
value={<FormatNumber value={expense.total_amount} />}
borderStyle={TotalLineBorderStyle.SingleDark}
/>
<TotalLine
title={<T id={'expense.details.total'} />}
value={<FormatNumber value={expense.formatted_amount} />}
borderStyle={TotalLineBorderStyle.DoubleDark}
textStyle={TotalLineTextStyle.Bold}
/>
</ExpenseTotalLines>
</ExpenseDetailsFooterRoot>
);
}
export const ExpenseDetailsFooterRoot = styled.div``;
export const ExpenseTotalLines = styled(TotalLines)`
margin-left: auto;
`;

View File

@@ -1,58 +1,79 @@
import React from 'react';
import moment from 'moment';
import { defaultTo } from 'lodash';
import styled from 'styled-components';
import { DetailItem, DetailsMenu, Money } from 'components';
import { FormattedMessage as T } from 'components';
import {
CommercialDocHeader,
CommercialDocTopHeader,
Row,
Col,
DetailItem,
DetailsMenu,
Money,
FormattedMessage as T,
} from 'components';
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
import { ExpenseDetailsStatus } from './components';
/**
* Expense drawer content.
*/
export default function ExpenseDrawerHeader() {
const {
expense: {
total_amount,
payment_date,
currency_code,
reference_no,
description,
published_at,
},
} = useExpenseDrawerContext();
const { expense } = 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>
<CommercialDocHeader>
<CommercialDocTopHeader>
<DetailsMenu>
<DetailItem name={'amount'} label={<T id={'full_amount'} />}>
<h3 class="big-number">{expense.formatted_amount}</h3>
</DetailItem>
<DetailItem name={'date'} label={<T id={'date'} />}>
{moment(payment_date).format('YYYY MMM DD')}
</DetailItem>
<StatusDetailItem>
<ExpenseDetailsStatus expense={expense} />
</StatusDetailItem>
</DetailsMenu>
</CommercialDocTopHeader>
<DetailItem name={'currency'} label={<T id={'currency'} />}>
{currency_code}
</DetailItem>
<Row>
<Col xs={6}>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem name={'date'} label={<T id={'date'} />}>
{moment(expense.payment_date).format('YYYY MMM DD')}
</DetailItem>
<DetailItem name={'reference'} label={<T id={'reference_no'} />}>
{defaultTo(reference_no, '-')}
</DetailItem>
<DetailItem name={'reference'} label={<T id={'reference_no'} />}>
{defaultTo(expense.reference_no, '-')}
</DetailItem>
<DetailItem label={<T id={'published_at'} />}>
{moment(published_at).format('YYYY MMM DD')}
</DetailItem>
</DetailsMenu>
<DetailItem label={<T id={'description'} />}>
{defaultTo(expense.description, '—')}
</DetailItem>
</DetailsMenu>
</Col>
<DetailsMenu direction={'horizantal'}>
<DetailItem label={<T id={'description'} />}>
{defaultTo(description, '—')}
</DetailItem>
<DetailItem label={<T id={'created_at'} />}>2021 Aug 24</DetailItem>
</DetailsMenu>
</div>
<Col xs={6}>
<DetailsMenu
textAlign={'right'}
direction={'horizantal'}
minLabelSize={'180px'}
>
<DetailItem label={<T id={'published_at'} />}>
{moment(expense.published_at).format('YYYY MMM DD')}
</DetailItem>
<DetailItem label={<T id={'created_at'} />}>2021 Aug 24</DetailItem>
</DetailsMenu>
</Col>
</Row>
</CommercialDocHeader>
);
}
const StatusDetailItem = styled(DetailItem)`
width: 50%;
text-align: right;
position: relative;
top: -5px;
`;

View File

@@ -1,18 +1,27 @@
import React from 'react';
import { DataTable } from 'components';
import { CommercialDocEntriesTable } from 'components';
import { useExpenseReadEntriesColumns } from './utils';
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
import { TableStyle } from '../../../common';
/**
* Expense details table.
*/
export default function ExpenseDrawerTable() {
// Expense readonly entries columns.
const columns = useExpenseReadEntriesColumns();
// Expense drawer context.
const { expense } = useExpenseDrawerContext();
return (
<div className="expense-drawer__content--table">
<DataTable columns={columns} data={expense.categories} />
</div>
<CommercialDocEntriesTable
columns={columns}
data={expense.categories}
styleName={TableStyle.Constrant}
/>
);
}

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { Tag, Intent } from '@blueprintjs/core';
import { T } from 'components';
/**
* Expense details status.
* @returns {React.JSX}
*/
export function ExpenseDetailsStatus({ expense }) {
return expense.is_published ? (
<Tag round={true} minimal={true}>
<T id={'published'} />
</Tag>
) : (
<Tag round={true} intent={Intent.WARNING}>
<T id={'draft'} />
</Tag>
);
}

View File

@@ -22,7 +22,7 @@ function ExpenseDrawer({
<Drawer
isOpen={isOpen}
name={name}
title={intl.get('expense')}
title={intl.get('expense.drawer.title')}
size={'65%'}
style={{ minWidth: '700px', maxWidth: '900px' }}
>

View File

@@ -1,26 +1,48 @@
import React from 'react';
import clsx from 'classnames';
import { Card } from 'components';
import { Tab } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { DrawerMainTabs } from 'components';
import InventoryAdjustmentDetailTab from './InventoryAdjustmentDetailTab';
import InventoryAdjustmentDetailActionsBar from './InventoryAdjustmentDetailActionsBar';
import InventoryAdjustmentDetailHeader from './InventoryAdjustmentDetailHeader';
import InventoryAdjustmentDetailTable from './InventoryAdjustmentDetailTable';
import InventoryAdjustmentDrawerCls from 'style/components/Drawers/InventoryAdjustmentDrawer.module.scss';
import InventoryAdjustmentDetailGLEntriesPanel from './InventoryAdjustmentDetailGLEntriesPanel';
/**
* Inventory adjustment detail
* @returns {React.JSX}
*/
export default function InventoryAdjustmentDetail() {
return (
<div className={clsx(InventoryAdjustmentDrawerCls.detail_panel)}>
<InventoryAdjustmentDetailsRoot>
<InventoryAdjustmentDetailActionsBar />
<Card>
<InventoryAdjustmentDetailHeader />
<InventoryAdjustmentDetailTable />
</Card>
</div>
<InventoryAdjustmentDetailTabs />
</InventoryAdjustmentDetailsRoot>
);
}
/**
* Invenoty adjusment details tabs.
* @returns {React.JSX}
*/
function InventoryAdjustmentDetailTabs() {
return (
<DrawerMainTabs
renderActiveTabPanelOnly={true}
defaultSelectedTabId="details"
>
<Tab
title={intl.get('details')}
id={'details'}
panel={<InventoryAdjustmentDetailTab />}
/>
<Tab
title={intl.get('journal_entries')}
id={'journal_entries'}
panel={<InventoryAdjustmentDetailGLEntriesPanel />}
/>
</DrawerMainTabs>
);
}
const InventoryAdjustmentDetailsRoot = styled.div``;

View File

@@ -1,13 +1,11 @@
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, Can } from 'components';
import { Icon, DrawerActionsBar, FormattedMessage as T, Can } from 'components';
import {
InventoryAdjustmentAction,
AbilitySubject,
@@ -34,7 +32,7 @@ function InventoryAdjustmentDetailActionsBar({
I={InventoryAdjustmentAction.Delete}
a={AbilitySubject.InventoryAdjustment}
>
<DashboardActionsBar>
<DrawerActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
@@ -44,7 +42,7 @@ function InventoryAdjustmentDetailActionsBar({
onClick={handleDeleteInventoryAdjustment}
/>
</NavbarGroup>
</DashboardActionsBar>
</DrawerActionsBar>
</Can>
);
}

View File

@@ -0,0 +1,42 @@
import React from 'react';
import styled from 'styled-components';
import { Card } from 'components';
import { useTransactionsByReference } from 'hooks/query';
import { useInventoryAdjustmentDrawerContext } from './InventoryAdjustmentDrawerProvider';
import JournalEntriesTable, {
AmountDisplayedBaseCurrencyMessage,
} from '../../JournalEntriesTable/JournalEntriesTable';
/**
* Inentory adjustmet detail GL entries panel.
* @returns {React.JSX}
*/
export default function InventoryAdjustmentDetailGLEntriesPanel() {
const { inventoryId } = useInventoryAdjustmentDrawerContext();
// Handle fetch transaction by reference.
const {
data: { transactions },
isLoading: isTransactionLoading,
} = useTransactionsByReference(
{
reference_id: inventoryId,
reference_type: 'inventoryAdjustment',
},
{ enabled: !!inventoryId },
);
return (
<InventoryAdjustmentGLEntriesRoot>
<AmountDisplayedBaseCurrencyMessage />
<JournalEntriesTable
loading={isTransactionLoading}
transactions={transactions}
/>
</InventoryAdjustmentGLEntriesRoot>
);
}
const InventoryAdjustmentGLEntriesRoot = styled(Card)``;

View File

@@ -14,43 +14,37 @@ import InventoryAdjustmentDrawerCls from 'style/components/Drawers/InventoryAdju
* Inventory detail header.
*/
export default function InventoryAdjustmentDetailHeader() {
const {
inventoryAdjustment: {
date,
type,
adjustment_account,
inventory_direction,
description,
reference_no,
reason,
published_at,
created_at,
},
} = useInventoryAdjustmentDrawerContext();
const { inventoryAdjustment } = useInventoryAdjustmentDrawerContext();
return (
<div className={clsx(InventoryAdjustmentDrawerCls.detail_panel_header)}>
<DetailsMenu>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem label={intl.get('date')}>
{moment(date).format('YYYY MMM DD')}
{moment(inventoryAdjustment.date).format('YYYY MMM DD')}
</DetailItem>
<DetailItem label={intl.get('type')}>{type}</DetailItem>
<DetailItem label={intl.get('type')}>
{inventoryAdjustment.formatted_type}
</DetailItem>
<DetailItem label={intl.get('adjustment_account')}>
{adjustment_account.name}
{inventoryAdjustment.adjustment_account.name}
</DetailItem>
<DetailItem name={'reference'} label={intl.get('reference_no')}>
{defaultTo(reference_no, '-')}
{defaultTo(inventoryAdjustment.reference_no, '-')}
</DetailItem>
<DetailItem label={intl.get('published_at')}>
{moment(published_at).format('YYYY MMM DD')}
{moment(inventoryAdjustment.published_at).format('YYYY MMM DD')}
</DetailItem>
</DetailsMenu>
<DetailsMenu direction={'horizantal'}>
<DetailItem label={intl.get('reason')}>
{defaultTo(reason, '—')}
{defaultTo(inventoryAdjustment.reason, '—')}
</DetailItem>
<DetailItem label={intl.get('created_at')}>
{moment(created_at).format('YYYY MMM DD')}
{moment(inventoryAdjustment.created_at).format('YYYY MMM DD')}
</DetailItem>
</DetailsMenu>
</div>

View File

@@ -0,0 +1,16 @@
import React from 'react';
import styled from 'styled-components';
import { CommercialDocBox } from 'components';
import InventoryAdjustmentDetailHeader from './InventoryAdjustmentDetailHeader';
import InventoryAdjustmentDetailTable from './InventoryAdjustmentDetailTable';
export default function InventoryAdjustmentDetailTab() {
return (
<CommercialDocBox>
<InventoryAdjustmentDetailHeader />
<InventoryAdjustmentDetailTable />
</CommercialDocBox>
);
}

View File

@@ -1,30 +1,24 @@
import React from 'react';
import clsx from 'classnames';
import { DataTable } from 'components';
import { CommercialDocEntriesTable } 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() {
// Inventory adjustment entries columns.
const columns = useInventoryAdjustmentEntriesColumns();
const {
inventoryAdjustment: { entries },
} = useInventoryAdjustmentDrawerContext();
// Inventory adjustment details drawer context.
const { inventoryAdjustment } = useInventoryAdjustmentDrawerContext();
return (
<div className={clsx(InventoryAdjustmentDrawerCls.detail_panel_table)}>
<DataTable
columns={columns}
data={entries}
className={'table-constrant'}
/>
</div>
<CommercialDocEntriesTable
columns={columns}
data={inventoryAdjustment.entries}
className={'table-constrant'}
/>
);
}

View File

@@ -19,7 +19,12 @@ function InventoryAdjustmentDetailDrawer({
payload: { inventoryId },
}) {
return (
<Drawer isOpen={isOpen} name={name} size={'750px'}>
<Drawer
isOpen={isOpen}
name={name}
style={{ minWidth: '700px', maxWidth: '900px' }}
size={'65%'}
>
<DrawerSuspense>
<InventoryAdjustmentDrawerContent inventoryId={inventoryId} />
</DrawerSuspense>

View File

@@ -5,7 +5,7 @@ export const useInventoryAdjustmentEntriesColumns = () =>
React.useMemo(
() => [
{
Header: intl.get('product_and_service'),
Header: intl.get('inventory_adjustment.column.product'),
accessor: 'item.name',
width: 150,
className: 'name',

View File

@@ -1,41 +1,66 @@
import React from 'react';
import { Tab } from '@blueprintjs/core';
import styled from 'styled-components';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import { useAbilityContext } from 'hooks/utils';
import { DrawerMainTabs } from 'components';
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
import {
PaymentReceiveAction,
AbilitySubject,
} from '../../../common/abilityOption';
import InvoiceDetailActionsBar from './InvoiceDetailActionsBar';
import InvoiceGLEntriesTable from './InvoiceGLEntriesTable';
import InvoicePaymentTransactionsTable from './InvoicePaymentTransactions/InvoicePaymentTransactionsTable';
import InvoiceDetailTab from './InvoiceDetailTab';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss';
/**
* Invoice details tabs.
* @returns {React.JSX}
*/
function InvoiceDetailsTabs() {
const ability = useAbilityContext();
return (
<DrawerMainTabs
renderActiveTabPanelOnly={true}
defaultSelectedTabId="details"
>
<Tab
title={intl.get('overview')}
id={'details'}
panel={<InvoiceDetailTab />}
/>
<Tab
title={intl.get('journal_entries')}
id={'journal_entries'}
panel={<InvoiceGLEntriesTable />}
/>
{ability.can(
PaymentReceiveAction.View,
AbilitySubject.PaymentReceive,
) && (
<Tab
title={intl.get('payment_transactions')}
id={'payment_transactions'}
panel={<InvoicePaymentTransactionsTable />}
/>
)}
</DrawerMainTabs>
);
}
/**
* Invoice view detail.
* @returns {React.JSX}
*/
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} />}
/>
{/* <Tab
title={intl.get('payment_transactions')}
id={'payment_transactions'}
// panel={}
/> */}
</DrawerMainTabs>
</div>
<InvoiceDetailsRoot>
<InvoiceDetailActionsBar />
<InvoiceDetailsTabs />
</InvoiceDetailsRoot>
);
}
export const InvoiceDetailsRoot = styled.div``;

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
@@ -8,7 +7,6 @@ import {
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
@@ -16,7 +14,13 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { If, Can, Icon, FormattedMessage as T } from 'components';
import {
If,
Can,
Icon,
DrawerActionsBar,
FormattedMessage as T,
} from 'components';
import {
SaleInvoiceAction,
PaymentReceiveAction,
@@ -24,7 +28,6 @@ import {
} from '../../../common/abilityOption';
import { compose } from 'utils';
import { BadDebtMenuItem } from './utils';
/**
@@ -51,6 +54,14 @@ function InvoiceDetailActionsBar({
closeDrawer('invoice-detail-drawer');
};
// Handle convert to invoice.
const handleConvertToCreitNote = () => {
history.push(`/credit-notes/new?from_invoice_id=${invoiceId}`, {
invoiceId: invoiceId,
});
closeDrawer('invoice-detail-drawer');
};
// Handle delete sale invoice.
const handleDeleteInvoice = () => {
openAlert('invoice-delete', { invoiceId });
@@ -81,7 +92,7 @@ function InvoiceDetailActionsBar({
};
return (
<DashboardActionsBar>
<DrawerActionsBar>
<NavbarGroup>
<Can I={SaleInvoiceAction.Edit} a={AbilitySubject.Invoice}>
<Button
@@ -90,14 +101,13 @@ function InvoiceDetailActionsBar({
text={<T id={'edit_invoice'} />}
onClick={handleEditInvoice}
/>
<NavbarDivider />
</Can>
<Can I={PaymentReceiveAction.Create} a={AbilitySubject.PaymentReceive}>
<If condition={invoice.is_delivered && !invoice.is_fully_paid}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="quick-payment-16" iconSize={16} />}
icon={<Icon icon="arrow-downward" iconSize={18} />}
text={<T id={'add_payment'} />}
onClick={handleQuickPaymentInvoice}
/>
@@ -120,19 +130,20 @@ function InvoiceDetailActionsBar({
intent={Intent.DANGER}
onClick={handleDeleteInvoice}
/>
<NavbarDivider />
</Can>
<Can I={SaleInvoiceAction.Writeoff} a={AbilitySubject.Invoice}>
<NavbarDivider />
<BadDebtMenuItem
payload={{
onBadDebt: handleBadDebtInvoice,
onCancelBadDebt: handleCancelBadDebtInvoice,
onNotifyViaSMS: handleNotifyViaSMS,
onConvert: handleConvertToCreitNote,
}}
/>
</Can>
</NavbarGroup>
</DashboardActionsBar>
</DrawerActionsBar>
);
}

View File

@@ -1,13 +1,12 @@
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.
* @returns {React.JSX}
*/
export default function InvoiceDetailDrawerContent({
// #ownProp

View File

@@ -1,41 +1,30 @@
import React from 'react';
import intl from 'react-intl-universal';
import { DrawerHeaderContent, DrawerLoading } from 'components';
import { useTransactionsByReference, useInvoice } from 'hooks/query';
import { 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.
// Provider.
const provider = {
transactions,
invoiceId,
invoice,
};
return (
<DrawerLoading loading={isTransactionLoading || isInvoiceLoading}>
<DrawerLoading loading={isInvoiceLoading}>
<DrawerHeaderContent
name="invoice-detail-drawer"
title={intl.get('invoice_details')}
title={intl.get('invoice_details.drawer.title', {
invoiceNumber: invoice.invoice_no,
})}
/>
<InvoiceDetailDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>

View File

@@ -1,40 +1,39 @@
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 {
CommercialDocFooter,
T,
If,
DetailsMenu,
DetailItem,
} from 'components';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
/**
* Invoice details footer.
* @returns {React.JSX}
*/
export function InvoiceDetailFooter() {
const { invoice } = useInvoiceDetailDrawerContext();
if (!invoice.terms_conditions && !invoice.invoice_message) {
return null;
}
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>
<CommercialDocFooter>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<If condition={invoice.terms_conditions}>
<DetailItem label={<T id={'terms_conditions'} />}>
{invoice.terms_conditions}
</DetailItem>
</If>
<If condition={invoice.invoice_message}>
<DetailItem label={<T id={'invoice.details.invoice_message'} />}>
{invoice.invoice_message}
</DetailItem>
</If>
</DetailsMenu>
</CommercialDocFooter>
);
}

View File

@@ -1,12 +1,21 @@
import React from 'react';
import intl from 'react-intl-universal';
import { defaultTo } from 'lodash';
import clsx from 'classnames';
import styled from 'styled-components';
import { DetailsMenu, DetailItem, FormatDate } from 'components';
import {
ButtonLink,
Row,
Col,
DetailsMenu,
DetailItem,
FormatDate,
CommercialDocHeader,
CommercialDocTopHeader,
CustomerDrawerLink,
} from 'components';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss';
import { InvoiceDetailsStatus } from './utils';
/**
* Invoice detail header.
@@ -14,44 +23,81 @@ import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss
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>
const handleCustomerLinkClick = () => {};
<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>
return (
<CommercialDocHeader>
<CommercialDocTopHeader>
<DetailsMenu>
<AmountDetailItem label={intl.get('amount')}>
<h3 class="big-number">{invoice.formatted_amount}</h3>
</AmountDetailItem>
<StatusDetailItem label={''}>
<InvoiceDetailsStatus invoice={invoice} />
</StatusDetailItem>
</DetailsMenu>
</CommercialDocTopHeader>
<Row>
<Col xs={6}>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem label={intl.get('invoice_date')}>
<FormatDate value={invoice.invoice_date} />
</DetailItem>
<DetailItem label={intl.get('due_date')}>
<FormatDate value={invoice.due_date} />
</DetailItem>
<DetailItem label={intl.get('customer_name')}>
<CustomerDrawerLink customerId={invoice.customer_id}>
{invoice.customer?.display_name}
</CustomerDrawerLink>
</DetailItem>
<DetailItem label={intl.get('invoice.details.invoice_no')}>
{invoice.invoice_no}
</DetailItem>
</DetailsMenu>
</Col>
<Col xs={6}>
<DetailsMenu
direction={'horizantal'}
minLabelSize={'180px'}
textAlign={'right'}
>
<DetailItem label={intl.get('due_amount')}>
<strong>{invoice.formatted_due_amount}</strong>
</DetailItem>
<DetailItem label={intl.get('invoice.details.payment_amount')}>
<strong>{invoice.formatted_payment_amount}</strong>
</DetailItem>
<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>
</Col>
</Row>
</CommercialDocHeader>
);
}
const StatusDetailItem = styled(DetailItem)`
width: 50%;
text-align: right;
position: relative;
top: -5px;
`;
const AmountDetailItem = styled(DetailItem)`
width: 50%;
`;

View File

@@ -1,28 +1,22 @@
import React from 'react';
import clsx from 'classnames';
import { Card } from 'components';
import { CommercialDocBox } from 'components';
import InvoiceDetailActionsBar from './InvoiceDetailActionsBar';
import InvoiceDetailHeader from './InvoiceDetailHeader';
import InvoiceDetailTable from './InvoiceDetailTable';
import { InvoiceDetailTableFooter } from './InvoiceDetailTableFooter';
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>
<CommercialDocBox>
<InvoiceDetailHeader />
<InvoiceDetailTable />
<InvoiceDetailTableFooter />
<InvoiceDetailFooter />
</CommercialDocBox>
);
}
}

View File

@@ -1,30 +1,29 @@
import React from 'react';
import clsx from 'classnames';
import { DataTable } from 'components';
import { CommercialDocEntriesTable } from 'components';
import { useInvoiceReadonlyEntriesColumns } from './utils';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss';
import { TableStyle } from '../../../common';
/**
* Invoice readonly details entries table columns.
*/
export default function InvoiceDetailTable() {
// Invoice readonly entries table columns.
const columns = useInvoiceReadonlyEntriesColumns();
// Invoice details drawer context.
const {
invoice: { entries },
} = useInvoiceDetailDrawerContext();
return (
<div className={clsx(InvoiceDrawerCls.detail_panel_table)}>
<DataTable
columns={columns}
data={entries}
className={'table-constrant'}
/>
</div>
<CommercialDocEntriesTable
columns={columns}
data={entries}
styleName={TableStyle.Constrant}
/>
);
}
}

View File

@@ -0,0 +1,52 @@
import React from 'react';
import styled from 'styled-components';
import {
T,
TotalLines,
FormatNumber,
TotalLine,
TotalLineBorderStyle,
TotalLineTextStyle,
} from 'components';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
/**
* Invoice details footer.
*/
export function InvoiceDetailTableFooter() {
const { invoice } = useInvoiceDetailDrawerContext();
return (
<InvoiceDetailsFooterRoot>
<InvoiceTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine
title={<T id={'invoice.details.subtotal'} />}
value={<FormatNumber value={invoice.balance} />}
borderStyle={TotalLineBorderStyle.SingleDark}
/>
<TotalLine
title={<T id={'invoice.details.total'} />}
value={invoice.formatted_amount}
borderStyle={TotalLineBorderStyle.DoubleDark}
textStyle={TotalLineTextStyle.Bold}
/>
<TotalLine
title={<T id={'invoice.details.payment_amount'} />}
value={invoice.formatted_payment_amount}
/>
<TotalLine
title={<T id={'invoice.details.due_amount'} />}
value={invoice.formatted_due_amount}
textStyle={TotalLineTextStyle.Bold}
/>
</InvoiceTotalLines>
</InvoiceDetailsFooterRoot>
);
}
const InvoiceDetailsFooterRoot = styled.div``;
const InvoiceTotalLines = styled(TotalLines)`
margin-left: auto;
`;

View File

@@ -0,0 +1,45 @@
import React from 'react';
import styled from 'styled-components';
import { Card } from 'components';
import { useTransactionsByReference } from 'hooks/query';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
import JournalEntriesTable, {
AmountDisplayedBaseCurrencyMessage,
} from '../../JournalEntriesTable/JournalEntriesTable';
/**
* Invoice GL entries table.
* @returns {React.JSX}
*/
export default function InvoiceGLEntriesTable() {
const { invoiceId } = useInvoiceDetailDrawerContext();
// Handle fetch transaction by reference.
const {
data: { transactions },
isLoading: isTransactionLoading,
} = useTransactionsByReference(
{
reference_id: invoiceId,
reference_type: 'SaleInvoice',
},
{ enabled: !!invoiceId },
);
return (
<InvoiceGLEntriesRoot>
<AmountDisplayedBaseCurrencyMessage />
<InvoiceGLEntriesDatatable
loading={isTransactionLoading}
transactions={transactions}
/>
</InvoiceGLEntriesRoot>
);
}
const InvoiceGLEntriesDatatable = styled(JournalEntriesTable)``;
const InvoiceGLEntriesRoot = styled(Card)``;

View File

@@ -0,0 +1,82 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { DataTable, Card } from 'components';
import {
useInvoicePaymentTransactionsColumns,
ActionsMenu,
} from './components';
import { useInvoiceDetailDrawerContext } from '../InvoiceDetailDrawerProvider';
import { useInvoicePaymentTransactions } from 'hooks/query';
import { TableStyle } from '../../../../common';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
/**
* Invoice payment transactions datatable.
*/
function InvoicePaymentTransactionsTable({
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
// Invoice payment transactions table columns.
const columns = useInvoicePaymentTransactionsColumns();
// Invoice drawer context.
const { invoiceId } = useInvoiceDetailDrawerContext();
// Fetch invoice payment transactions.
const {
data: paymentTransactions,
isFetching: isPaymentTransactionFetching,
isLoading: isPaymentTransactionLoading,
} = useInvoicePaymentTransactions(invoiceId, {
enabled: !!invoiceId,
});
// Handles delete payment transactions.
const handleDeletePaymentTransactons = ({ payment_receive_id }) => {
openAlert('payment-receive-delete', {
paymentReceiveId: payment_receive_id,
});
};
// Handles edit payment transactions.
const handleEditPaymentTransactions = ({ payment_receive_id }) => {
history.push(`/payment-receives/${payment_receive_id}/edit`);
closeDrawer('invoice-detail-drawer');
};
return (
<Card>
<DataTable
columns={columns}
data={paymentTransactions}
loading={isPaymentTransactionLoading}
headerLoading={isPaymentTransactionLoading}
progressBarLoading={isPaymentTransactionFetching}
TableLoadingRenderer={TableSkeletonRows}
styleName={TableStyle.Constrant}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeletePaymentTransactons,
onEdit: handleEditPaymentTransactions,
}}
/>
</Card>
);
}
export default compose(
withAlertsActions,
withDrawerActions,
)(InvoicePaymentTransactionsTable);

View File

@@ -0,0 +1,93 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import clsx from 'classnames';
import { CLASSES } from '../../../../common/classes';
import { FormatDateCell, Icon, Can } from '../../../../components';
import { safeCallback } from 'utils';
import {
PaymentReceiveAction,
AbilitySubject,
} from '../../../../common/abilityOption';
/**
* Table actions menu.
*/
export function ActionsMenu({
row: { original },
payload: { onEdit, onDelete },
}) {
return (
<Menu>
<Can I={PaymentReceiveAction.Edit} a={AbilitySubject.PaymentReceive}>
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('invoice_transactions.action.edit_transaction')}
onClick={safeCallback(onEdit, original)}
/>
</Can>
<Can I={PaymentReceiveAction.Delete} a={AbilitySubject.PaymentReceive}>
<MenuDivider />
<MenuItem
text={intl.get('invoice_transactions.action.delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Can>
</Menu>
);
}
/**
* Retrieve invoice payment transactions table columns.
*/
export const useInvoicePaymentTransactionsColumns = () => {
return React.useMemo(
() => [
{
id: 'date',
Header: intl.get('payment_date'),
accessor: 'formatted_payment_date',
Cell: FormatDateCell,
width: 110,
className: 'date',
textOverview: true,
},
{
id: 'deposit_account_name',
Header: intl.get('invoice_transactions.column.withdrawal_account'),
accessor: 'deposit_account_name',
width: 120,
textOverview: true,
},
{
id: 'amount',
Header: intl.get('amount'),
accessor: 'formatted_payment_amount',
align: 'right',
width: 120,
className: clsx(CLASSES.FONT_BOLD),
textOverview: true,
},
{
id: 'payment_number.',
Header: intl.get('payment_no'),
accessor: 'payment_number',
width: 100,
className: 'payment_number',
textOverview: true,
},
{
id: 'payment_reference_no',
Header: intl.get('reference_no'),
accessor: 'payment_reference_no',
width: 90,
className: 'payment_reference_no',
clickable: true,
textOverview: true,
},
],
[],
);
};

View File

@@ -21,7 +21,7 @@ function InvoiceDetailDrawer({
<Drawer
isOpen={isOpen}
name={name}
style={{ minWidth: '700px', maxWidth: '900px' }}
style={{ minWidth: '700px', maxWidth: '1000px' }}
size={'65%'}
>
<DrawerSuspense>

View File

@@ -1,5 +1,6 @@
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import {
Button,
Popover,
@@ -7,9 +8,20 @@ import {
Position,
MenuItem,
Menu,
Intent,
Tag,
} from '@blueprintjs/core';
import { Icon, FormattedMessage as T, Choose } from 'components';
import { FormatNumberCell } from '../../../components';
import {
FormatNumberCell,
Icon,
FormattedMessage as T,
Choose,
Can,
} from 'components';
import {
SaleInvoiceAction,
AbilitySubject,
} from '../../../common/abilityOption';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
/**
@@ -59,8 +71,12 @@ export const useInvoiceReadonlyEntriesColumns = () =>
[],
);
/**
* Invoice details more actions menu.
* @returns {React.JSX}
*/
export const BadDebtMenuItem = ({
payload: { onCancelBadDebt, onBadDebt, onNotifyViaSMS },
payload: { onCancelBadDebt, onBadDebt, onNotifyViaSMS, onConvert },
}) => {
const { invoice } = useInvoiceDetailDrawerContext();
@@ -88,10 +104,18 @@ export const BadDebtMenuItem = ({
/>
</Choose.When>
</Choose>
<MenuItem
onClick={onNotifyViaSMS}
text={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
/>
<Can I={SaleInvoiceAction.Edit} a={AbilitySubject.Invoice}>
<MenuItem
onClick={onConvert}
text={<T id={'invoice.convert_to_credit_note'} />}
/>
</Can>
<Can I={SaleInvoiceAction.NotifyBySms} a={AbilitySubject.Invoice}>
<MenuItem
onClick={onNotifyViaSMS}
text={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
/>
</Can>
</Menu>
}
>
@@ -99,3 +123,44 @@ export const BadDebtMenuItem = ({
</Popover>
);
};
/**
* Invoice details status.
* @returns {React.JSX}
*/
export function InvoiceDetailsStatus({ invoice }) {
return (
<Choose>
<Choose.When condition={invoice.is_fully_paid && invoice.is_delivered}>
<StatusTag intent={Intent.SUCCESS} round={true}>
<T id={'paid'} />
</StatusTag>
</Choose.When>
<Choose.When condition={invoice.is_delivered}>
<Choose>
<Choose.When condition={invoice.is_overdue}>
<StatusTag intent={Intent.WARNING} round={true}>
<T id={'overdue'} />
</StatusTag>
</Choose.When>
<Choose.Otherwise>
<StatusTag intent={Intent.PRIMARY} round={true}>
<T id={'delivered'} />
</StatusTag>
</Choose.Otherwise>
</Choose>
</Choose.When>
<Choose.Otherwise>
<StatusTag round={true} minimal={true}>
<T id={'draft'} />
</StatusTag>
</Choose.Otherwise>
</Choose>
);
}
const StatusTag = styled(Tag)`
min-width: 65px;
text-align: center;
`;

View File

@@ -1,9 +1,7 @@
import React from 'react';
import ItemDetailTab from './ItemDetailTab';
import ItemDetailActionsBar from './ItemDetailActionsBar';
import ItemDetailHeader from './ItemDetailHeader';
import { Card } from 'components';
/**
* Item detail.
@@ -12,10 +10,7 @@ export default function ItemDetail() {
return (
<div className="item-drawer">
<ItemDetailActionsBar />
<Card>
<ItemDetailHeader />
</Card>
<ItemDetailTab />
</div>
);
}

View File

@@ -8,6 +8,9 @@ const ItemDetailDrawerContext = React.createContext();
* Item detail provider
*/
function ItemDetailDrawerProvider({ itemId, ...props }) {
// transaction type payload.
const [value, setValue] = React.useState('invoices');
// Fetches the given item detail.
const { isLoading: isItemLoading, data: item } = useItem(itemId, {
enabled: !!itemId,
@@ -18,6 +21,8 @@ function ItemDetailDrawerProvider({ itemId, ...props }) {
item,
itemId,
isItemLoading,
value,
setValue,
};
return (

View File

@@ -3,7 +3,7 @@ import intl from 'react-intl-universal';
import { defaultTo } from 'lodash';
import classNames from 'classnames';
import { If, DetailsMenu, DetailItem } from 'components';
import { If, DetailsMenu, DetailItem, Card } from 'components';
import { useItemDetailDrawerContext } from './ItemDetailDrawerProvider';
/**
@@ -13,70 +13,72 @@ 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('sell_account_id')}
children={defaultTo(item?.sell_account?.name, '-')}
/>
<DetailItem
label={intl.get('cost_account_id')}
children={defaultTo(item.cost_account?.name, '-')}
/>
<If condition={item.type === 'inventory'}>
<Card>
<div class="item-drawer__content">
<DetailsMenu direction={'vertical'}>
<DetailItem
label={intl.get('inventory_account')}
children={defaultTo(item?.inventory_account?.name, '-')}
name={'name'}
label={intl.get('item_name')}
children={item.name}
/>
</If>
<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>
<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_formatted} />
<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('sell_account_id')}
children={defaultTo(item?.sell_account?.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('item.sell_description')}
children={defaultTo(item.sell_description, '-')}
/>
<DetailItem
label={intl.get('item.purchase_description')}
children={defaultTo(item.purchase_description, '-')}
/>
</DetailsMenu>
</div>
</Card>
);
}

View File

@@ -0,0 +1,23 @@
import React from 'react';
import { Tab } from '@blueprintjs/core';
import { DrawerMainTabs, FormattedMessage as T } from 'components';
import { ItemPaymentTransactions } from './ItemPaymentTransactions';
import ItemDetailHeader from './ItemDetailHeader';
export default function ItemDetailTab() {
return (
<DrawerMainTabs renderActiveTabPanelOnly={true}>
<Tab
id={'overview'}
title={<T id={'overview'} />}
panel={<ItemDetailHeader />}
/>
<Tab
id={'transactions'}
title={<T id={'transactions'} />}
panel={<ItemPaymentTransactions />}
/>
</DrawerMainTabs>
);
}

View File

@@ -0,0 +1,100 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import clsx from 'classnames';
import { CLASSES } from '../../../../../common/classes';
import { Can, FormatDateCell, Icon } from '../../../../../components';
import { safeCallback } from 'utils';
import {
BillAction,
AbilitySubject,
} from '../../../../../common/abilityOption';
/**
* Table actions menu.
*/
export function ActionsMenu({
row: { original },
payload: { onEdit, onDelete },
}) {
return (
<Menu>
<Can I={BillAction.Edit} a={AbilitySubject.Bill}>
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('invoice_transactions.action.edit_transaction')}
onClick={safeCallback(onEdit, original)}
/>
</Can>
<Can I={BillAction.Delete} a={AbilitySubject.Bill}>
<MenuDivider />
<MenuItem
text={intl.get('invoice_transactions.action.delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Can>
</Menu>
);
}
/**
* Retrieve bill transactions associated with item table columns.
*/
export const useBillTransactionsColumns = () => {
return React.useMemo(
() => [
{
id: 'bill_date',
Header: intl.get('date'),
accessor: 'formatted_bill_date',
Cell: FormatDateCell,
width: 120,
className: 'bill_date',
},
{
id: 'vendor',
Header: intl.get('vendor'),
accessor: 'vendor_display_name',
width: 140,
className: 'vendor',
textOverview: true,
},
{
id: 'bill_number',
Header: intl.get('bill_number'),
accessor: (row) => (row.bill_number ? `${row.bill_number}` : null),
width: 100,
className: 'bill_number',
},
{
id: 'qunatity',
Header: intl.get('item.drawer_quantity_sold'),
accessor: 'quantity',
align: 'right',
width: 100,
},
{
id: 'rate',
Header: 'Rate',
accessor: 'formatted_rate',
align: 'right',
width: 100,
className: clsx(CLASSES.FONT_BOLD),
textOverview: true,
},
{
id: 'amount',
Header: intl.get('total'),
accessor: 'formatted_amount',
align: 'right',
width: 100,
className: clsx(CLASSES.FONT_BOLD),
textOverview: true,
},
],
[],
);
};

View File

@@ -0,0 +1,77 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { DataTable } from '../../../../../components';
import { TableStyle } from 'common';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows'
import { useItemDetailDrawerContext } from '../../ItemDetailDrawerProvider';
import { useItemAssociatedBillTransactions } from 'hooks/query';
import { useBillTransactionsColumns, ActionsMenu } from './components';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
/**
* Bill payment transactions data table.
*/
function BillPaymentTransactions({
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
const columns = useBillTransactionsColumns();
const { itemId } = useItemDetailDrawerContext();
// Handle fetch Estimate associated transactions.
const {
isLoading: isBillTransactionsLoading,
isFetching: isBillTransactionFetching,
data: paymentTransactions,
} = useItemAssociatedBillTransactions(itemId, {
enabled: !!itemId,
});
// Handles delete payment transactions.
const handleDeletePaymentTransactons = ({ bill_id }) => {
openAlert('bill-delete', {
billId: bill_id,
});
};
// Handles edit payment transactions.
const handleEditPaymentTransactions = ({ bill_id }) => {
history.push(`/bills/${bill_id}/edit`);
closeDrawer('item-detail-drawer');
};
return (
<DataTable
columns={columns}
data={paymentTransactions}
loading={isBillTransactionsLoading}
headerLoading={isBillTransactionsLoading}
progressBarLoading={isBillTransactionFetching}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditPaymentTransactions,
onDelete: handleDeletePaymentTransactons,
}}
styleName={TableStyle.Constrant}
TableLoadingRenderer={TableSkeletonRows}
/>
);
}
export default compose(
withAlertsActions,
withDrawerActions,
)(BillPaymentTransactions);

View File

@@ -0,0 +1,102 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import clsx from 'classnames';
import { CLASSES } from '../../../../../common/classes';
import { Can, FormatDateCell, Icon } from '../../../../../components';
import { safeCallback } from 'utils';
import {
SaleEstimateAction,
AbilitySubject,
} from '../../../../../common/abilityOption';
/**
* Table actions menu.
*/
export function ActionsMenu({
row: { original },
payload: { onEdit, onDelete },
}) {
return (
<Menu>
<Can I={SaleEstimateAction.Edit} a={AbilitySubject.Estimate}>
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('invoice_transactions.action.edit_transaction')}
onClick={safeCallback(onEdit, original)}
/>
</Can>
<Can I={SaleEstimateAction.Delete} a={AbilitySubject.Estimate}>
<MenuDivider />
<MenuItem
text={intl.get('invoice_transactions.action.delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Can>
</Menu>
);
}
/**
* Retrieve estimate transactions associated with item table columns.
*/
export const useEstimateTransactionsColumns = () => {
return React.useMemo(
() => [
{
id: 'estimate_date',
Header: intl.get('date'),
accessor: 'formatted_estimate_date',
Cell: FormatDateCell,
width: 120,
className: 'estimate_date',
textOverview: true,
},
{
id: 'customer',
Header: intl.get('customer'),
accessor: 'customer_display_name',
width: 140,
className: 'customer',
textOverview: true,
},
{
id: 'estimate_number',
Header: intl.get('estimate_no'),
accessor: 'estimate_number',
width: 120,
className: 'estimate_number',
textOverview: true,
},
{
id: 'qunatity',
Header: intl.get('item.drawer_quantity_sold'),
accessor: 'quantity',
align: 'right',
width: 100,
},
{
id: 'rate',
Header: 'Rate',
accessor: 'formatted_rate',
align: 'right',
width: 100,
className: clsx(CLASSES.FONT_BOLD),
textOverview: true,
},
{
id: 'amount',
Header: intl.get('total'),
accessor: 'formatted_amount',
align: 'right',
width: 100,
className: clsx(CLASSES.FONT_BOLD),
textOverview: true,
},
],
[],
);
};

View File

@@ -0,0 +1,75 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { DataTable } from '../../../../../components';
import { TableStyle } from 'common';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import { useItemDetailDrawerContext } from '../../ItemDetailDrawerProvider';
import { useItemAssociatedEstimateTransactions } from 'hooks/query';
import { useEstimateTransactionsColumns, ActionsMenu } from './components';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
/**
* Esimtate payment transactions.
*/
function EstimatePaymentTransactions({
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
const columns = useEstimateTransactionsColumns();
const { itemId } = useItemDetailDrawerContext();
// Handle fetch Estimate associated transactions.
const {
isLoading: isEstimateTransactionsLoading,
isFetching: isEstimateTransactionFetching,
data: paymentTransactions,
} = useItemAssociatedEstimateTransactions(itemId, {
enabled: !!itemId,
});
// Handles delete payment transactions.
const handleDeletePaymentTransactons = ({ estimate_id }) => {
openAlert('estimate-delete', {
estimateId: estimate_id,
});
};
// Handles edit payment transactions.
const handleEditPaymentTransactions = ({ estimate_id }) => {
history.push(`/estimates/${estimate_id}/edit`);
closeDrawer('item-detail-drawer');
};
return (
<DataTable
columns={columns}
data={paymentTransactions}
loading={isEstimateTransactionsLoading}
headerLoading={isEstimateTransactionsLoading}
progressBarLoading={isEstimateTransactionFetching}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditPaymentTransactions,
onDelete: handleDeletePaymentTransactons,
}}
styleName={TableStyle.Constrant}
TableLoadingRenderer={TableSkeletonRows}
/>
);
}
export default compose(
withAlertsActions,
withDrawerActions,
)(EstimatePaymentTransactions);

View File

@@ -0,0 +1,102 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import clsx from 'classnames';
import { CLASSES } from '../../../../../common/classes';
import { Can, FormatDateCell, Icon } from '../../../../../components';
import { safeCallback } from 'utils';
import {
SaleInvoiceAction,
AbilitySubject,
} from '../../../../../common/abilityOption';
/**
* Table actions menu.
*/
export function ActionsMenu({
row: { original },
payload: { onEdit, onDelete },
}) {
return (
<Menu>
<Can I={SaleInvoiceAction.Edit} a={AbilitySubject.Invoice}>
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('invoice_transactions.action.edit_transaction')}
onClick={safeCallback(onEdit, original)}
/>
</Can>
<Can I={SaleInvoiceAction.Delete} a={AbilitySubject.Invoice}>
<MenuDivider />
<MenuItem
text={intl.get('invoice_transactions.action.delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Can>
</Menu>
);
}
/**
* Retrieve invoice payment transactions associated with item table columns.
*/
export const useInvoicePaymentTransactionsColumns = () => {
return React.useMemo(
() => [
{
id: 'invoice_date',
Header: intl.get('date'),
accessor: 'formatted_invoice_date',
Cell: FormatDateCell,
width: 120,
className: 'invoice_date',
textOverview: true,
},
{
id: 'customer',
Header: intl.get('customer'),
accessor: 'customer_display_name',
width: 140,
className: 'customer',
textOverview: true,
},
{
id: 'invoice_no',
Header: intl.get('invoice_no__'),
accessor: 'invoice_number',
width: 120,
className: 'invoice_no',
textOverview: true,
},
{
id: 'qunatity',
Header: intl.get('item.drawer_quantity_sold'),
accessor: 'quantity',
align: 'right',
width: 100,
},
{
id: 'rate',
Header: intl.get('rate'),
accessor: 'formatted_rate',
align: 'right',
width: 100,
className: clsx(CLASSES.FONT_BOLD),
textOverview: true,
},
{
id: 'amount',
Header: intl.get('total'),
accessor: 'formatted_amount',
align: 'right',
width: 100,
className: clsx(CLASSES.FONT_BOLD),
textOverview: true,
},
],
[],
);
};

View File

@@ -0,0 +1,78 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { DataTable } from '../../../../../components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows'
import { useItemAssociatedInvoiceTransactions } from 'hooks/query';
import { useItemDetailDrawerContext } from '../../ItemDetailDrawerProvider';
import {
useInvoicePaymentTransactionsColumns,
ActionsMenu,
} from './components';
import { TableStyle } from 'common';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
/**
* Invoice payment transactions.
*/
function InvoicePaymentTransactions({
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
const columns = useInvoicePaymentTransactionsColumns();
const { itemId } = useItemDetailDrawerContext();
// Handle fetch invoice associated transactions.
const {
isLoading: isInvoiceTransactionsLoading,
isFetching: isInvoiceTransactionFetching,
data: paymentTransactions,
} = useItemAssociatedInvoiceTransactions(itemId, {
enabled: !!itemId,
});
// Handles delete payment transactions.
const handleDeletePaymentTransactons = ({ invoice_id }) => {
openAlert('invoice-delete', {
invoiceId: invoice_id,
});
};
// Handles edit payment transactions.
const handleEditPaymentTransactions = ({ invoice_id }) => {
history.push(`/invoices/${invoice_id}/edit`);
closeDrawer('item-detail-drawer');
};
return (
<DataTable
columns={columns}
data={paymentTransactions}
loading={isInvoiceTransactionsLoading}
headerLoading={isInvoiceTransactionsLoading}
progressBarLoading={isInvoiceTransactionFetching}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditPaymentTransactions,
onDelete: handleDeletePaymentTransactons,
}}
styleName={TableStyle.Constrant}
TableLoadingRenderer={TableSkeletonRows}
/>
);
}
export default compose(
withAlertsActions,
withDrawerActions,
)(InvoicePaymentTransactions);

View File

@@ -0,0 +1,22 @@
import React from 'react';
import InvoicePaymentTransactions from './InvoicePaymentTransactions';
import EstimatePaymentTransactions from './EstimatePaymentTransactions';
import ReceiptPaymentTransactions from './ReceiptPaymentTransactions';
import BillPaymentTransactions from './BillPaymentTransactions';
export default function ItemPaymentTransactionsContent({ tansactionType }) {
const handleType = () => {
switch (tansactionType) {
case 'invoices':
default:
return <InvoicePaymentTransactions />;
case 'estimates':
return <EstimatePaymentTransactions />;
case 'receipts':
return <ReceiptPaymentTransactions />;
case 'bills':
return <BillPaymentTransactions />;
}
};
return handleType();
}

View File

@@ -0,0 +1,103 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import clsx from 'classnames';
import { CLASSES } from '../../../../../common/classes';
import { Can, FormatDateCell, Icon } from '../../../../../components';
import { safeCallback } from 'utils';
import {
SaleReceiptAction,
AbilitySubject,
} from '../../../../../common/abilityOption';
/**
* Table actions menu.
*/
export function ActionsMenu({
row: { original },
payload: { onEdit, onDelete },
}) {
return (
<Menu>
<Can I={SaleReceiptAction.Edit} a={AbilitySubject.Receipt}>
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('invoice_transactions.action.edit_transaction')}
onClick={safeCallback(onEdit, original)}
/>
</Can>
<Can I={SaleReceiptAction.Edit} a={AbilitySubject.Receipt}>
<MenuDivider />
<MenuItem
text={intl.get('invoice_transactions.action.delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Can>
</Menu>
);
}
/**
* Retrieve receipt transactions associated with item table columns.
*/
export const useReceiptTransactionsColumns = () => {
return React.useMemo(
() => [
{
id: 'receipt_date',
Header: intl.get('date'),
accessor: 'formatted_receipt_date',
Cell: FormatDateCell,
width: 120,
className: 'receipt_date',
textOverview: true,
},
{
id: 'customer',
Header: intl.get('customer'),
accessor: 'customer_display_name',
width: 140,
className: 'customer',
textOverview: true,
},
{
id: 'receipt_number',
Header: intl.get('receipt_no'),
accessor: 'receip_number',
width: 120,
className: 'receipt_number',
clickable: true,
textOverview: true,
},
{
id: 'qunatity',
Header: intl.get('item.drawer_quantity_sold'),
accessor: 'quantity',
align: 'right',
width: 100,
},
{
id: 'rate',
Header: 'Rate',
accessor: 'formatted_rate',
align: 'right',
width: 100,
className: clsx(CLASSES.FONT_BOLD),
textOverview: true,
},
{
id: 'amount',
Header: intl.get('total'),
accessor: 'formatted_amount',
align: 'right',
width: 100,
className: clsx(CLASSES.FONT_BOLD),
textOverview: true,
},
],
[],
);
};

View File

@@ -0,0 +1,76 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { DataTable } from '../../../../../components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows'
import { TableStyle } from 'common';
import { useItemDetailDrawerContext } from '../../ItemDetailDrawerProvider';
import { useItemAssociatedReceiptTransactions } from 'hooks/query';
import { useReceiptTransactionsColumns, ActionsMenu } from './components';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
/**
* Receipt payment transactions.
*/
function ReceiptPaymentTransactions({
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
const columns = useReceiptTransactionsColumns();
const { itemId } = useItemDetailDrawerContext();
// Handle fetch receipts associated transactions.
const {
isLoading: isReceiptTransactionsLoading,
isFetching: isReceiptTransactionFetching,
data: paymentTransactions,
} = useItemAssociatedReceiptTransactions(itemId, {
enabled: !!itemId,
});
// Handles delete payment transactions.
const handleDeletePaymentTransactons = ({ receipt_id }) => {
openAlert('receipt-delete', {
receiptId: receipt_id,
});
};
// Handles edit payment transactions.
const handleEditPaymentTransactions = ({ receipt_id }) => {
history.push(`/receipts/${receipt_id}/edit`);
closeDrawer('item-detail-drawer');
};
return (
<DataTable
columns={columns}
data={paymentTransactions}
loading={isReceiptTransactionsLoading}
headerLoading={isReceiptTransactionsLoading}
progressBarLoading={isReceiptTransactionFetching}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditPaymentTransactions,
onDelete: handleDeletePaymentTransactons,
}}
styleName={TableStyle.Constrant}
TableLoadingRenderer={TableSkeletonRows}
/>
);
}
export default compose(
withAlertsActions,
withDrawerActions,
)(ReceiptPaymentTransactions);

View File

@@ -0,0 +1,42 @@
import React from 'react';
import styled from 'styled-components';
import { Card } from 'components';
import { ItemManuTransaction } from './utils';
import ItemPaymentTransactionContent from './ItemPaymentTransactionContent';
import { useItemDetailDrawerContext } from '../ItemDetailDrawerProvider';
export function ItemPaymentTransactions() {
const { value } = useItemDetailDrawerContext();
return (
<Card>
<ItemTransactionsHeader />
<ItemPaymentTransactionContent tansactionType={value} />
</Card>
);
}
/**
* Item transactions header.
* @returns {React.JSX}
*/
export function ItemTransactionsHeader() {
const { setValue } = useItemDetailDrawerContext();
// handle item change.
const handleItemChange = (item) => {
setValue(item);
};
return (
<ItemTransactionsHeaderRoot>
<ItemManuTransaction onChange={handleItemChange} />
</ItemTransactionsHeaderRoot>
);
}
export const ItemTransactionsHeaderRoot = styled.div`
margin-bottom: 10px;
`;

View File

@@ -0,0 +1,65 @@
import React from 'react';
import {
Button,
MenuItem,
Menu,
Popover,
PopoverInteractionKind,
Position,
} from '@blueprintjs/core';
import styled from 'styled-components';
import { FormattedMessage as T } from 'components';
import { useItemDetailDrawerContext } from '../ItemDetailDrawerProvider';
import { useGetItemPaymentTransactionsMenu } from '../../../../common/itemPaymentTranactionsOption';
export const ItemManuTransaction = ({ onChange }) => {
const { value, setValue } = useItemDetailDrawerContext();
const itemTransactionMenu = useGetItemPaymentTransactionsMenu();
if (itemTransactionMenu.length === 0) {
return null;
}
const handleClickItem = (item) => {
onChange && onChange(item);
};
const content = itemTransactionMenu.map(({ name, label }) => (
<MenuItem onClick={() => handleClickItem(name)} text={label} />
));
return (
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={<Menu>{content}</Menu>}
>
<ItemSwitchButton
minimal={true}
text={<T id={'item.drawer_transactions_by'} />}
rightIcon={'caret-down'}
>
<ItemSwitchText>
<T id={value} />
</ItemSwitchText>
</ItemSwitchButton>
</Popover>
);
};
const ItemSwitchButton = styled(Button)`
.bp3-button-text {
display: flex;
color: #727983;
}
`;
const ItemSwitchText = styled.span`
font-weight: 600;
color: #33304a;
padding-left: 3px;
`;

View File

@@ -19,7 +19,12 @@ function ItemDetailDrawer({
payload: { itemId },
}) {
return (
<Drawer isOpen={isOpen} name={name} size={'750px'}>
<Drawer
isOpen={isOpen}
name={name}
style={{ minWidth: '700px', maxWidth: '900px' }}
size={'65%'}
>
<DrawerSuspense>
<ItemDetailDrawerContent itemId={itemId} />
</DrawerSuspense>

View File

@@ -8,9 +8,8 @@ import {
Intent,
NavbarDivider,
} from '@blueprintjs/core';
import { Can, FormattedMessage as T } from 'components';
import { DrawerActionsBar, Can, FormattedMessage as T } from 'components';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
@@ -47,7 +46,7 @@ function ManualJournalDrawerActionBar({
};
return (
<DashboardActionsBar>
<DrawerActionsBar>
<NavbarGroup>
<Can I={ManualJournalAction.Edit} a={AbilitySubject.ManualJournal}>
<Button
@@ -68,7 +67,7 @@ function ManualJournalDrawerActionBar({
/>
</Can>
</NavbarGroup>
</DashboardActionsBar>
</DrawerActionsBar>
);
}

View File

@@ -1,31 +1,28 @@
import React from 'react';
import styled from 'styled-components';
import { Card } from 'components';
import { CommercialDocBox } 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} />
<ManualJournalDetailsRoot>
<ManualJournalDrawerActionBar />
<div className="journal-drawer__content">
<Card>
<ManualJournalDrawerHeader />
<ManualJournalDrawerTable />
<ManualJournalDrawerFooter />
</Card>
</div>
</div>
<CommercialDocBox>
<ManualJournalDrawerHeader />
<ManualJournalDrawerTable />
<ManualJournalDrawerFooter />
</CommercialDocBox>
</ManualJournalDetailsRoot>
);
}
const ManualJournalDetailsRoot = styled.div``;

View File

@@ -1,7 +1,15 @@
import React from 'react';
import { useManualJournalDrawerContext } from './ManualJournalDrawerProvider';
import styled from 'styled-components';
import { T, FormatNumber } from '../../../components';
import { useManualJournalDrawerContext } from './ManualJournalDrawerProvider';
import {
TRDarkSingleLine,
TRDarkDoubleLines,
T,
FormatNumber,
Table,
TD,
} from '../../../components';
/**
* Manual journal readonly details footer.
@@ -13,26 +21,41 @@ export default function ManualJournalDrawerFooter() {
return (
<div className="journal-drawer__content-footer">
<div class="total-lines">
<div class="total-lines__line total-lines__line--subtotal">
<div class="title">
<JournalTotalTable>
<TRDarkSingleLine>
<TDLabel>
<T id={'manual_journal.details.subtotal'} />
</div>
<div class="debit">
</TDLabel>
<TDAmount textAlign={'right'}>
<FormatNumber value={amount} />
</div>
<div class="credit">
</TDAmount>
<TDAmount textAlign={'right'}>
<FormatNumber value={amount} />
</div>
</div>
<div class="total-lines__line total-lines__line--total">
<div class="title">
</TDAmount>
</TRDarkSingleLine>
<TRDarkDoubleLines>
<TDLabel>
<T id={'manual_journal.details.total'} />
</div>
<div class="debit">{formatted_amount}</div>
<div class="credit">{formatted_amount}</div>
</div>
</div>
</TDLabel>
<TDAmount textAlign={'right'}>{formatted_amount}</TDAmount>
<TDAmount textAlign={'right'}>{formatted_amount}</TDAmount>
</TRDarkDoubleLines>
</JournalTotalTable>
</div>
);
}
const JournalTotalTable = styled(Table)`
font-weight: 600;
width: auto;
margin-left: auto;
`;
const TDLabel = styled(TD)`
width: 220px;
`;
const TDAmount = styled(TD)`
width: 155px;
`;

View File

@@ -1,53 +1,71 @@
import React from 'react';
import { defaultTo } from 'lodash';
import { DetailsMenu, DetailItem, FormattedMessage as T } from 'components';
import styled from 'styled-components';
import {
Row,
Col,
DetailsMenu,
DetailItem,
FormattedMessage as T,
CommercialDocHeader,
CommercialDocTopHeader,
} from 'components';
import { ManualJournalDetailsStatus } from './utils';
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();
const { manualJournal } = useManualJournalDrawerContext();
return (
<div className={'journal-drawer__content-header'}>
<DetailsMenu>
<DetailItem name={'total'} label={<T id={'total'} />}>
<h3 class="amount">{formatted_amount}</h3>
</DetailItem>
<CommercialDocHeader>
<CommercialDocTopHeader>
<DetailsMenu>
<DetailItem name={'total'} label={<T id={'total'} />}>
<h3 class="big-number">{manualJournal.formatted_amount}</h3>
</DetailItem>
<DetailItem name={'journal-type'} label={<T id={'journal_type'} />}>
{journal_type}
</DetailItem>
<StatusDetailItem>
<ManualJournalDetailsStatus manualJournal={manualJournal} />
</StatusDetailItem>
</DetailsMenu>
</CommercialDocTopHeader>
<DetailItem name={'journal-number'} label={<T id={'journal_no'} />}>
{journal_number}
</DetailItem>
<Row>
<Col xs={6}>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem name={'journal-type'} label={<T id={'journal_type'} />}>
{manualJournal.journal_type}
</DetailItem>
<DetailItem name={'reference-no'} label={<T id={'reference_no'} />}>
{defaultTo(reference, '-')}
</DetailItem>
<DetailItem name={'journal-number'} label={<T id={'journal_no'} />}>
{manualJournal.journal_number}
</DetailItem>
<DetailItem name={'currency'} label={<T id={'currency'} />}>
{currency_code}
</DetailItem>
</DetailsMenu>
<DetailItem name={'reference-no'} label={<T id={'reference_no'} />}>
{defaultTo(manualJournal.reference, '-')}
</DetailItem>
<div class="journal-drawer__content-description">
<b class="title">
<T id={'manual_journal.details.description'} />
</b>
: {defaultTo(description, '—')}
</div>
</div>
<DetailItem name={'currency'} label={<T id={'currency'} />}>
{manualJournal.currency_code}
</DetailItem>
<DetailItem label={<T id={'description'} />}>
{defaultTo(manualJournal.description, '—')}
</DetailItem>
</DetailsMenu>
</Col>
</Row>
</CommercialDocHeader>
);
}
const StatusDetailItem = styled(DetailItem)`
width: 50%;
text-align: right;
position: relative;
top: -5px;
`;

View File

@@ -31,7 +31,7 @@ function ManualJournalDrawerProvider({ manualJournalId, ...props }) {
<DrawerLoading loading={isJournalLoading}>
<DrawerHeaderContent
name={'journal-drawer'}
title={intl.get('manual_journal_number', {
title={intl.get('manual_journal.drawer.title', {
number: manualJournal?.journal_number,
})}
/>

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