re-structure to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 01:02:31 +02:00
parent 8242ec64ba
commit 7a0a13f9d5
10400 changed files with 46966 additions and 17223 deletions

View File

@@ -0,0 +1,95 @@
// @ts-nocheck
import React from 'react';
import {
Button,
Classes,
NavbarGroup,
Intent,
NavbarDivider,
} from '@blueprintjs/core';
import {
DashboardActionsBar,
Icon,
Can,
FormattedMessage as T,
} from '@/components';
import { AccountAction, AbilitySubject } from '@/constants/abilityOption';
import { DialogsName } from '@/constants/dialogs';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import { AccountDialogAction } from '@/containers/Dialogs/AccountDialog/utils';
import { useAccountDrawerContext } from './AccountDrawerProvider';
import { compose, safeCallback } from '@/utils';
/**
* Account drawer action bar.
*/
function AccountDrawerActionBar({
// #withDialog
openDialog,
// #withAlertsDialog
openAlert,
}) {
// Account drawer context.
const { account } = useAccountDrawerContext();
// Handle new child button click.
const onNewChildAccount = () => {
openDialog(DialogsName.AccountForm, {
action: AccountDialogAction.NewChild,
parentAccountId: account.id,
accountType: account.account_type,
});
};
// Handle edit account action.
const onEditAccount = () => {
openDialog(DialogsName.AccountForm, {
action: AccountDialogAction.Edit,
accountId: account.id,
});
};
// Handle delete action account.
const onDeleteAccount = () => {
openAlert('account-delete', { accountId: account.id });
};
return (
<DashboardActionsBar>
<NavbarGroup>
<Can I={AccountAction.Edit} a={AbilitySubject.Account}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'edit_account'} />}
onClick={safeCallback(onEditAccount)}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="plus" />}
text={<T id={'new_child_account'} />}
onClick={safeCallback(onNewChildAccount)}
/>
<NavbarDivider />
</Can>
<Can I={AccountAction.Delete} a={AbilitySubject.Account}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={safeCallback(onDeleteAccount)}
/>
</Can>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withDialogActions,
withAlertsActions,
)(AccountDrawerActionBar);

View File

@@ -0,0 +1,25 @@
// @ts-nocheck
import React from 'react';
import { DrawerBody } from '@/components';
import '@/style/components/Drawers/AccountDrawer.scss';
import { AccountDrawerProvider } from './AccountDrawerProvider';
import AccountDrawerDetails from './AccountDrawerDetails';
/**
* Account drawer content.
*/
export default function AccountDrawerContent({
// #ownProp
accountId,
name,
}) {
return (
<AccountDrawerProvider name={name} accountId={accountId}>
<DrawerBody>
<AccountDrawerDetails />
</DrawerBody>
</AccountDrawerProvider>
);
}

View File

@@ -0,0 +1,24 @@
// @ts-nocheck
import React from 'react';
import { Card } from '@/components';
import AccountDrawerActionBar from './AccountDrawerActionBar';
import AccountDrawerHeader from './AccountDrawerHeader';
import AccountDrawerTable from './AccountDrawerTable';
/**
* Account view details.
*/
export default function AccountDrawerDetails() {
return (
<div className={'account-drawer'}>
<AccountDrawerActionBar />
<Card className={'card-header'}>
<AccountDrawerHeader />
</Card>
<AccountDrawerTable />
</div>
);
}

View File

@@ -0,0 +1,59 @@
// @ts-nocheck
import React from 'react';
import { isEmpty } from 'lodash';
import {
Icon,
DetailsMenu,
DetailItem,
FormattedMessage as T,
} from '@/components';
import { useAccountDrawerContext } from './AccountDrawerProvider';
/**
* Account drawer header.
*/
export default function AccountDrawerHeader() {
const { account } = useAccountDrawerContext();
return (
<div className={'account-drawer__content-header'}>
<DetailsMenu>
<DetailItem
name={'closing-balance'}
label={<T id={'closing_balance'} />}
>
<h3 class={'big-number'}>{account.formatted_amount}</h3>
</DetailItem>
<DetailItem name={'account-type'} label={<T id={'account_type'} />}>
{account.account_type_label}
</DetailItem>
<DetailItem name={'account-normal'} label={<T id={'account_normal'} />}>
{account.account_normal_formatted}
<Icon
iconSize={14}
icon={`arrow-${
account.account_normal === 'credit' ? 'down' : 'up'
}`}
/>
</DetailItem>
<DetailItem name={'code'} label={<T id={'code'} />}>
{account.code}
</DetailItem>
<DetailItem name={'currency'} label={<T id={'currency'} />}>
{account.currency_code}
</DetailItem>
</DetailsMenu>
<DetailsMenu direction={'horizantal'}>
<DetailItem name={'description'} label={<T id={'description'} />}>
{!isEmpty(account.description) ? account.description : '--'}
</DetailItem>
</DetailsMenu>
</div>
);
}

View File

@@ -0,0 +1,44 @@
// @ts-nocheck
import React from 'react';
import { useAccount, useAccountTransactions } from '@/hooks/query';
import { DrawerHeaderContent, DrawerLoading } from '@/components';
const AccountDrawerContext = React.createContext();
/**
* Account drawer provider.
*/
function AccountDrawerProvider({ accountId, name, ...props }) {
// Fetches the specific account details.
const { data: account, isLoading: isAccountLoading } = useAccount(accountId, {
enabled: !!accountId,
});
// Load the specific account transactions.
const { data: accounts, isLoading: isAccountsLoading } =
useAccountTransactions(accountId, {
enabled: !!accountId,
});
// Drawer title.
const drawerTitle = `${account.name} ${account.code}`;
// Provider.
const provider = {
accountId,
account,
accounts,
drawerName: name,
};
return (
<DrawerLoading loading={isAccountLoading || isAccountsLoading}>
<DrawerHeaderContent name={'account-drawer'} title={drawerTitle} />
<AccountDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useAccountDrawerContext = () => React.useContext(AccountDrawerContext);
export { AccountDrawerProvider, useAccountDrawerContext };

View File

@@ -0,0 +1,63 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { compose } from '@/utils';
import { TableStyle } from '@/constants';
import { Card, DataTable, If } from '@/components';
import { useAccountReadEntriesColumns } from './utils';
import { useAppIntlContext } from '@/components/AppIntlProvider';
import { useAccountDrawerContext } from './AccountDrawerProvider';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
/**
* account drawer table.
*/
function AccountDrawerTable({ closeDrawer }) {
const { account, accounts, drawerName } = useAccountDrawerContext();
// Account read-only entries table columns.
const columns = useAccountReadEntriesColumns();
// Handle view more link click.
const handleLinkClick = () => {
closeDrawer(drawerName);
};
// Application intl context.
const { isRTL } = useAppIntlContext();
return (
<Card>
<DataTable
columns={columns}
data={accounts}
payload={{ account }}
styleName={TableStyle.Constrant}
/>
<If condition={accounts.length > 0}>
<TableFooter>
<Link
to={`/financial-reports/general-ledger`}
onClick={handleLinkClick}
>
{isRTL ? '→' : '←'} {intl.get('view_more_transactions')}
</Link>
</TableFooter>
</If>
</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

@@ -0,0 +1,33 @@
// @ts-nocheck
import React, { lazy } from 'react';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
import { compose } from '@/utils';
const AccountDrawerContent = lazy(() => import('./AccountDrawerContent'));
/**
* Account drawer.
*/
function AccountDrawer({
name,
// #withDrawer
isOpen,
payload: { accountId },
}) {
return (
<Drawer
isOpen={isOpen}
name={name}
style={{ minWidth: '700px', maxWidth: '900px' }}
size={'65%'}
>
<DrawerSuspense>
<AccountDrawerContent name={name} accountId={accountId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers())(AccountDrawer);

View File

@@ -0,0 +1,70 @@
// @ts-nocheck
import intl from 'react-intl-universal';
import React from 'react';
import { FormatDateCell } from '@/components';
import { isBlank } from '@/utils';
/**
* Debit/credit table cell.
*/
function DebitCreditTableCell({ value, payload: { account } }) {
return !isBlank(value) && value !== 0 ? account.formatted_amount : null;
}
/**
* Running balance table cell.
*/
function RunningBalanceTableCell({ value, payload: { account } }) {
return account.formatted_amount;
}
/**
* Retrieve entries columns of read-only account view.
*/
export const useAccountReadEntriesColumns = () =>
React.useMemo(
() => [
{
Header: intl.get('transaction_date'),
accessor: 'date',
Cell: FormatDateCell,
width: 110,
textOverview: true,
},
{
Header: intl.get('transaction_type'),
accessor: 'reference_type_formatted',
width: 100,
textOverview: true,
},
{
Header: intl.get('credit'),
accessor: 'credit',
Cell: DebitCreditTableCell,
width: 80,
className: 'credit',
align: 'right',
textOverview: true,
},
{
Header: intl.get('debit'),
accessor: 'debit',
Cell: DebitCreditTableCell,
width: 80,
className: 'debit',
align: 'right',
textOverview: true,
},
{
Header: intl.get('running_balance'),
Cell: RunningBalanceTableCell,
accessor: 'running_balance',
width: 110,
className: 'running_balance',
align: 'right',
textOverview: true,
},
],
[],
);

View File

@@ -0,0 +1,128 @@
// @ts-nocheck
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import { useBillDrawerContext } from './BillDrawerProvider';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import {
Can,
If,
Icon,
DrawerActionsBar,
FormattedMessage as T,
} from '@/components';
import {
BillAction,
PaymentMadeAction,
AbilitySubject,
} from '@/constants/abilityOption';
import { BillMenuItem } from './utils';
import { safeCallback, compose } from '@/utils';
function BillDetailActionsBar({
// #withDialogActions
openDialog,
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
const { billId, bill } = useBillDrawerContext();
// Handle edit bill.
const onEditBill = () => {
history.push(`/bills/${billId}/edit`);
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 });
};
// Handle quick bill payment .
const handleQuickBillPayment = () => {
openDialog('quick-payment-made', { billId });
};
// Handle allocate landed cost button click.
const handleAllocateCostClick = () => {
openDialog('allocate-landed-cost', { billId });
};
return (
<DrawerActionsBar>
<NavbarGroup>
<Can I={BillAction.Edit} a={AbilitySubject.Bill}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'edit_bill'} />}
onClick={safeCallback(onEditBill)}
/>
<NavbarDivider />
</Can>
<Can I={PaymentMadeAction.Create} a={AbilitySubject.PaymentMade}>
<If condition={bill.is_open && !bill.is_fully_paid}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="arrow-upward" iconSize={16} />}
text={<T id={'add_payment'} />}
onClick={handleQuickBillPayment}
/>
</If>
</Can>
<Can I={BillAction.Delete} a={AbilitySubject.Bill}>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={safeCallback(onDeleteBill)}
/>
</Can>
<Can I={BillAction.Edit} a={AbilitySubject.Bill}>
<NavbarDivider />
<BillMenuItem
payload={{
onConvert: handleConvertToVendorCredit,
onAllocateLandedCost: handleAllocateCostClick,
}}
/>
</Can>
</NavbarGroup>
</DrawerActionsBar>
);
}
export default compose(
withDialogActions,
withDrawerActions,
withAlertsActions,
)(BillDetailActionsBar);

View File

@@ -0,0 +1,28 @@
// @ts-nocheck
import React from 'react';
import {
CommercialDocFooter,
T,
If,
DetailsMenu,
DetailItem,
} from '@/components';
import { useBillDrawerContext } from './BillDrawerProvider';
/**
* Bill detail footer.
* @returns {React.JSX}
*/
export default function BillDetailFooter() {
const { bill } = useBillDrawerContext();
return (
<CommercialDocFooter>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<If condition={bill.note}>
<DetailItem label={<T id={'note'} />}>{bill.note}</DetailItem>
</If>
</DetailsMenu>
</CommercialDocFooter>
);
}

View File

@@ -0,0 +1,96 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { defaultTo } from 'lodash';
import {
FormatDate,
DetailsMenu,
DetailItem,
Row,
Col,
CommercialDocHeader,
CommercialDocTopHeader,
VendorDrawerLink,
ExchangeRateDetailItem,
} from '@/components';
import { useBillDrawerContext } from './BillDrawerProvider';
import { BillDetailsStatus } from './utils';
/**
* Bill detail header.
*/
export default function BillDetailHeader() {
const { bill } = useBillDrawerContext();
return (
<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>
<ExchangeRateDetailItem
exchangeRate={bill?.exchange_rate}
toCurrency={bill?.currency_code}
/>
</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

@@ -0,0 +1,22 @@
// @ts-nocheck
import React from 'react';
import BillDetailHeader from './BillDetailHeader';
import BillDetailTable from './BillDetailTable';
import BillDetailFooter from './BillDetailFooter';
import { CommercialDocBox } from '@/components';
import { BillDetailTableFooter } from './BillDetailTableFooter';
/**
* Bill detail panel tab.
*/
export default function BillDetailTab() {
return (
<CommercialDocBox>
<BillDetailHeader />
<BillDetailTable />
<BillDetailTableFooter />
<BillDetailFooter />
</CommercialDocBox>
);
}

View File

@@ -0,0 +1,26 @@
// @ts-nocheck
import React from 'react';
import { CommercialDocEntriesTable } from '@/components';
import { useBillDrawerContext } from './BillDrawerProvider';
import { useBillReadonlyEntriesTableColumns } from './utils';
import { TableStyle } from '@/constants';
export default function BillDetailTable() {
const {
bill: { entries },
} = useBillDrawerContext();
// Retrieve bill readonly entries table columns.
const columns = useBillReadonlyEntriesTableColumns();
return (
<CommercialDocEntriesTable
columns={columns}
data={entries}
styleName={TableStyle.Constrant}
/>
);
}

View File

@@ -0,0 +1,52 @@
// @ts-nocheck
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

@@ -0,0 +1,22 @@
// @ts-nocheck
import React from 'react';
import { DrawerBody } from '@/components';
import { BillDrawerProvider } from './BillDrawerProvider';
import BillDrawerDetails from './BillDrawerDetails';
/**
* Bill drawer content.
*/
export default function BillDrawerContent({
// #ownProp
billId,
}) {
return (
<BillDrawerProvider billId={billId}>
<DrawerBody>
<BillDrawerDetails />
</DrawerBody>
</BillDrawerProvider>
);
}

View File

@@ -0,0 +1,65 @@
// @ts-nocheck
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 { useAbilityContext } from '@/hooks/utils';
import { PaymentMadeAction, AbilitySubject } from '@/constants/abilityOption';
import BillDetailTab from './BillDetailTab';
import LocatedLandedCostTable from './LocatedLandedCostTable';
import BillGLEntriesTable from './BillGLEntriesTable';
import BillPaymentTransactionTable from './BillPaymentTransactions/BillPaymentTransactionTable';
import BillDetailActionsBar from './BillDetailActionsBar';
/**
* Bill details tabs.
*/
function BillDetailsTabs() {
const ability = useAbilityContext();
return (
<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('payment_transactions')}
id={'payment_transactions'}
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

@@ -0,0 +1,60 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { DrawerHeaderContent, DrawerLoading } from '@/components';
import { useBill, useBillLocatedLandedCost } from '@/hooks/query';
import { useFeatureCan } from '@/hooks/state';
import { Features } from '@/constants';
const BillDrawerContext = React.createContext();
/**
* Bill drawer provider.
*/
function BillDrawerProvider({ billId, ...props }) {
// Features guard.
const { featureCan } = useFeatureCan();
// Handle fetch bill details.
const { isLoading: isBillLoading, data: bill } = useBill(billId, {
enabled: !!billId,
});
// Handle fetch bill located landed cost transaction.
const { isLoading: isLandedCostLoading, data: transactions } =
useBillLocatedLandedCost(billId, {
enabled: !!billId,
});
//provider.
const provider = {
billId,
transactions,
bill,
};
const loading = isLandedCostLoading || isBillLoading;
return (
<DrawerLoading loading={loading}>
<DrawerHeaderContent
name="bill-drawer"
title={intl.get('bill.drawer.title', {
number: bill.bill_number ? `(${bill.bill_number})` : null,
})}
subTitle={
featureCan(Features.Branches)
? intl.get('bill.drawer.subtitle', {
value: bill.branch?.name,
})
: null
}
/>
<BillDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useBillDrawerContext = () => React.useContext(BillDrawerContext);
export { BillDrawerProvider, useBillDrawerContext };

View File

@@ -0,0 +1,50 @@
// @ts-nocheck
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,77 @@
// @ts-nocheck
import React from 'react';
import { useHistory } from 'react-router-dom';
import { DataTable, Card, TableSkeletonRows } from '@/components';
import { TableStyle } from '@/constants';
import { useBillPaymentTransactionsColumns, ActionsMenu } from './components';
import { useBillDrawerContext } from '../BillDrawerProvider';
import { useBillPaymentTransactions } from '@/hooks/query';
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,90 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import { safeCallback } from '@/utils';
import { Can, FormatDateCell, Icon } from '@/components';
import { CLASSES } from '@/constants/classes';
import { PaymentMadeAction, AbilitySubject } from '@/constants/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

@@ -0,0 +1,84 @@
// @ts-nocheck
import React from 'react';
import {
DataTable,
TableSkeletonRows,
Card,
FormattedMessage as T,
} from '@/components';
import { useLocatedLandedCostColumns, ActionsMenu } from './components';
import { useBillDrawerContext } from './BillDrawerProvider';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { TableStyle } from '@/constants';
import { compose } from '@/utils';
/**
* Located landed cost table.
*/
function LocatedLandedCostTable({
// #withAlertsActions
openAlert,
// #withDialogActions
openDialog,
// #withDrawerActions
openDrawer,
}) {
// Located landed cost table columns.
const columns = useLocatedLandedCostColumns();
// Bill drawer context.
const { transactions, billId } = useBillDrawerContext();
// Handle the transaction delete action.
const handleDeleteTransaction = ({ id }) => {
openAlert('bill-located-cost-delete', { BillId: id });
};
// Handle from transaction link click.
const handleFromTransactionClick = (original) => {
const { from_transaction_type, from_transaction_id } = original;
switch (from_transaction_type) {
case 'Expense':
openDrawer('expense-drawer', { expenseId: from_transaction_id });
break;
case 'Bill':
default:
openDrawer('bill-drawer', { billId: from_transaction_id });
break;
}
};
return (
<div>
<Card>
<DataTable
columns={columns}
data={transactions}
ContextMenu={ActionsMenu}
TableLoadingRenderer={TableSkeletonRows}
styleName={TableStyle.Constrant}
payload={{
onDelete: handleDeleteTransaction,
onFromTranscationClick: handleFromTransactionClick,
}}
/>
</Card>
</div>
);
}
export default compose(
withAlertsActions,
withDialogActions,
withDrawerActions,
)(LocatedLandedCostTable);

View File

@@ -0,0 +1,103 @@
// @ts-nocheck
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 { CLASSES } from '@/constants/classes';
import { Icon } from '@/components';
/**
* Actions menu.
*/
export function ActionsMenu({ row: { original }, payload: { onDelete } }) {
return (
<Menu>
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
/>
</Menu>
);
}
/**
* From transaction table cell.
*/
export function FromTransactionCell({
row: { original },
payload: { onFromTranscationClick },
}) {
// Handle the link click
const handleAnchorClick = () => {
onFromTranscationClick && onFromTranscationClick(original);
};
return (
<a href="#" onClick={handleAnchorClick}>
{original.from_transaction_type} {original.from_transaction_id}
</a>
);
}
/**
* 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.
*/
export function useLocatedLandedCostColumns() {
return React.useMemo(
() => [
{
Header: intl.get('name'),
accessor: NameAccessor,
width: 150,
textOverview: true,
},
{
Header: intl.get('amount'),
accessor: 'formatted_amount',
width: 100,
align: 'right',
textOverview: true,
className: clsx(CLASSES.FONT_BOLD),
},
{
id: 'from_transaction',
Header: intl.get('From transaction'),
Cell: FromTransactionCell,
width: 100,
textOverview: true,
},
{
Header: intl.get('allocation_method'),
accessor: 'allocation_method_formatted',
width: 100,
textOverview: true,
},
],
[],
);
}
const LabelName = styled.div``;
const LabelDescription = styled.div`
font-size: 12px;
margin-top: 2px;
display: block;
opacity: 0.75;
`;

View File

@@ -0,0 +1,33 @@
// @ts-nocheck
import React from 'react';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
import { compose } from '@/utils';
const BillDrawerContent = React.lazy(() => import('./BillDrawerContent'));
/**
* Bill drawer.
*/
function BillDrawer({
name,
// #withDrawer
isOpen,
payload: { billId },
}) {
return (
<Drawer
isOpen={isOpen}
name={name}
style={{ minWidth: '700px', maxWidth: '900px' }}
size={'65%'}
>
<DrawerSuspense>
<BillDrawerContent billId={billId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers())(BillDrawer);

View File

@@ -0,0 +1,161 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import {
Button,
Popover,
PopoverInteractionKind,
Position,
MenuItem,
Menu,
Intent,
Tag,
} from '@blueprintjs/core';
import {
FormatNumberCell,
TextOverviewTooltipCell,
FormattedMessage as T,
Choose,
Icon,
} from '@/components';
import { getColumnWidth } from '@/utils';
import { useBillDrawerContext } from './BillDrawerProvider';
/**
* Retrieve bill readonly details entries table columns.
*/
export const useBillReadonlyEntriesTableColumns = () => {
const {
bill: { entries },
} = useBillDrawerContext();
return React.useMemo(
() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
Cell: TextOverviewTooltipCell,
width: 150,
className: 'item',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('description'),
accessor: 'description',
Cell: TextOverviewTooltipCell,
className: 'description',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'quantity', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'rate', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'amount', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
],
[],
);
};
/**
* 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

@@ -0,0 +1,46 @@
// @ts-nocheck
import React from 'react';
import { Button, Classes, NavbarGroup, Intent } from '@blueprintjs/core';
import {
Can,
FormattedMessage as T,
DrawerActionsBar,
Icon,
} from '@/components';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import { useCashflowTransactionDrawerContext } from './CashflowTransactionDrawerProvider';
import { AbilitySubject, CashflowAction } from '@/constants/abilityOption';
import { compose } from '@/utils';
/**
* Cashflow transaction drawer action bar.
*/
function CashflowTransactionDrawerActionBar({
// #withAlertsDialog
openAlert,
}) {
const { referenceId } = useCashflowTransactionDrawerContext();
// Handle cashflow transaction delete action.
const handleDeleteCashflowTransaction = () => {
openAlert('account-transaction-delete', { referenceId });
};
return (
<Can I={CashflowAction.Delete} a={AbilitySubject.Cashflow}>
<DrawerActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="trash-16" iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeleteCashflowTransaction}
/>
</NavbarGroup>
</DrawerActionsBar>
</Can>
);
}
export default compose(withAlertsActions)(CashflowTransactionDrawerActionBar);

View File

@@ -0,0 +1,24 @@
// @ts-nocheck
import React from 'react';
import '@/style/components/Drawers/CashflowTransactionDrawer.scss';
import { DrawerBody } from '@/components';
import { CashflowTransactionDrawerProvider } from './CashflowTransactionDrawerProvider';
import CashflowTransactionDrawerDetails from './CashflowTransactionDrawerDetails';
/**
* Cash flow transction drawer content.
*/
export default function CashflowTransactionDrawerContent({
// #ownProp
referenceId,
}) {
return (
<CashflowTransactionDrawerProvider referenceId={referenceId}>
<DrawerBody>
<CashflowTransactionDrawerDetails />
</DrawerBody>
</CashflowTransactionDrawerProvider>
);
}

View File

@@ -0,0 +1,27 @@
// @ts-nocheck
import React from 'react';
import { Card } from '@/components';
import CashflowTransactionDrawerActionBar from './CashflowTransactionDrawerActionBar';
import CashflowTransactionDrawerHeader from './CashflowTransactionDrawerHeader';
import CashflowTransactionDrawerTable from './CashflowTransactionDrawerTable';
import CashflowTransactionDrawerFooter from './CashflowTransactionDrawerFooter';
/**
* Cashflow transaction view details.
*/
export default function CashflowTransactionDrawerDetails() {
return (
<div className={'cashflow-drawer'}>
<CashflowTransactionDrawerActionBar />
<div className="cashflow-drawer__content">
<Card>
<CashflowTransactionDrawerHeader />
<CashflowTransactionDrawerTable />
<CashflowTransactionDrawerFooter />
</Card>
</div>
</div>
);
}

View File

@@ -0,0 +1,35 @@
// @ts-nocheck
import React from 'react';
import { useCashflowTransactionDrawerContext } from './CashflowTransactionDrawerProvider';
import { T, FormatNumber } from '@/components';
export default function CashflowTransactionDrawerFooter() {
const {
cashflowTransaction: { formatted_amount },
} = useCashflowTransactionDrawerContext();
return (
<div className="cashflow-drawer__content-footer">
<div class="total-lines">
<div class="total-lines__line total-lines__line--subtotal">
<div class="title">
<T id={'manual_journal.details.subtotal'} />
</div>
<div class="debit">
<FormatNumber value={formatted_amount} />
</div>
<div class="credit">
<FormatNumber value={formatted_amount} />
</div>
</div>
<div class="total-lines__line total-lines__line--total">
<div class="title">
<T id={'manual_journal.details.total'} />
</div>
<div class="debit">{formatted_amount}</div>
<div class="credit">{formatted_amount}</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
// @ts-nocheck
import React from 'react';
import { defaultTo } from 'lodash';
import {
DetailsMenu,
DetailItem,
FormatDate,
FormattedMessage as T,
Row,
Col,
CommercialDocHeader,
} from '@/components';
import { useCashflowTransactionDrawerContext } from './CashflowTransactionDrawerProvider';
/**
* Cashlflow transaction drawer detail Header.
*/
export default function CashflowTransactionDrawerHeader() {
const { cashflowTransaction } = useCashflowTransactionDrawerContext();
return (
<CommercialDocHeader>
<CommercialDocHeader>
<DetailsMenu>
<DetailItem name={'total'} label={<T id={'total'} />}>
<h3 class="big-number">{cashflowTransaction.formatted_amount}</h3>
</DetailItem>
</DetailsMenu>
</CommercialDocHeader>
<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'} />}
>
{cashflowTransaction.transaction_number}
</DetailItem>
<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

@@ -0,0 +1,51 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { useCashflowTransaction } from '@/hooks/query';
import { DrawerLoading, DrawerHeaderContent } from '@/components';
const CashflowTransactionDrawerContext = React.createContext();
/**
* Cashflow transaction drawer provider.
*/
function CashflowTransactionDrawerProvider({ referenceId, ...props }) {
// Fetch the specific cashflow transaction details.
const {
data: cashflowTransaction,
isLoading: isCashflowTransactionLoading,
isFetching: isCashflowTransactionFetching,
} = useCashflowTransaction(referenceId, {
enabled: !!referenceId,
});
// Provider.
const provider = {
referenceId,
cashflowTransaction,
isCashflowTransactionFetching,
isCashflowTransactionLoading,
};
return (
<DrawerLoading loading={isCashflowTransactionLoading}>
<DrawerHeaderContent
name={'cashflow-transaction-drawer'}
title={intl.get('cash_flow.drawer.label_transaction', {
number: cashflowTransaction?.transaction_number,
})}
/>
<CashflowTransactionDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useCashflowTransactionDrawerContext = () =>
React.useContext(CashflowTransactionDrawerContext);
export {
CashflowTransactionDrawerProvider,
useCashflowTransactionDrawerContext,
};

View File

@@ -0,0 +1,19 @@
// @ts-nocheck
import React from 'react';
import { CommercialDocEntriesTable } from '@/components';
import { useCashflowTransactionColumns } from './utils';
import { useCashflowTransactionDrawerContext } from './CashflowTransactionDrawerProvider';
/**
* Cashflow transaction drawer table.
*/
export default function CashflowTransactionDrawerTable() {
const columns = useCashflowTransactionColumns();
const {
cashflowTransaction: { transactions },
} = useCashflowTransactionDrawerContext();
return <CommercialDocEntriesTable columns={columns} data={transactions} />;
}

View File

@@ -0,0 +1,36 @@
// @ts-nocheck
import React from 'react';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
import { compose } from '@/utils';
const CashFlowTransactionDrawerContent = React.lazy(() =>
import('./CashflowTransactionDrawerContent'),
);
/**
* Cash flow transaction drawer
*/
function CashflowTransactionDetailDrawer({
name,
// #withDrawer
isOpen,
payload: { referenceId },
}) {
return (
<Drawer
isOpen={isOpen}
name={name}
size={'65%'}
style={{ minWidth: '700px', maxWidth: '900px' }}
>
<DrawerSuspense>
<CashFlowTransactionDrawerContent referenceId={referenceId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers())(CashflowTransactionDetailDrawer);

View File

@@ -0,0 +1,51 @@
// @ts-nocheck
import intl from 'react-intl-universal';
import React from 'react';
import { FormatNumberCell } from '@/components';
/**
* Retrieve cashflow transaction entries columns.
*/
export const useCashflowTransactionColumns = () =>
React.useMemo(
() => [
{
Header: intl.get('account_name'),
accessor: 'account.name',
width: 130,
disableSortBy: true,
className: 'account',
},
{
Header: intl.get('contact'),
accessor: 'contact.display_name',
width: 130,
disableSortBy: true,
className: 'contact',
},
{
Header: intl.get('credit'),
accessor: 'credit',
Cell: FormatNumberCell,
width: 100,
disableResizable: true,
disableSortBy: true,
formatNumber: { noZero: true },
className: 'credit',
align: 'right',
},
{
Header: intl.get('debit'),
accessor: 'debit',
Cell: FormatNumberCell,
width: 100,
disableResizable: true,
disableSortBy: true,
formatNumber: { noZero: true },
className: 'debit',
align: 'right',
},
],
[],
);

View File

@@ -0,0 +1,19 @@
// @ts-nocheck
import React from 'react';
import ContactDetailActionsBar from './ContactDetailActionsBar';
import ContactDetailList from './ContactDetailList';
import { Card } from '@/components';
/**
* contact detail.
*/
export default function ContactDetail() {
return (
<div className="view-detail-drawer">
<ContactDetailActionsBar />
<Card>
<ContactDetailList />
</Card>
</div>
);
}

View File

@@ -0,0 +1,73 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import { useContactDetailDrawerContext } from './ContactDetailDrawerProvider';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DashboardActionsBar, Icon, FormattedMessage as T } from '@/components';
import { safeCallback, compose } from '@/utils';
function ContactDetailActionsBar({
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const { contact, contactId } = useContactDetailDrawerContext();
const history = useHistory();
// Handle edit contact.
const onEditContact = () => {
return contactId
? (history.push(`/${contact?.contact_service}s/${contactId}/edit`),
closeDrawer('contact-detail-drawer'))
: null;
};
// Handle delete contact.
const onDeleteContact = () => {
return contactId
? (openAlert(`${contact?.contact_service}-delete`, { contactId }),
closeDrawer('contact-detail-drawer'))
: null;
};
return (
<DashboardActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={intl.get('edit_contact', { name: contact?.contact_service })}
onClick={safeCallback(onEditContact)}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={safeCallback(onDeleteContact)}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withDrawerActions,
withAlertsActions,
)(ContactDetailActionsBar);

View File

@@ -0,0 +1,21 @@
// @ts-nocheck
import React from 'react';
import ContactDetail from './ContactDetail';
import { ContactDetailDrawerProvider } from './ContactDetailDrawerProvider';
import '@/style/components/Drawers/ViewDetail/ViewDetail.scss';
/**
* Contact detail drawer content.
*/
export default function ContactDetailDrawerContent({
// #ownProp
contact,
}) {
return (
<ContactDetailDrawerProvider contactId={contact}>
<ContactDetail />
</ContactDetailDrawerProvider>
);
}

View File

@@ -0,0 +1,37 @@
// @ts-nocheck
import React from 'react';
import { DrawerHeaderContent, DashboardInsider } from '@/components';
import { useContact } from '@/hooks/query';
const ContactDetailDrawerContext = React.createContext();
/**
* Contact detail provider.
*/
function ContactDetailDrawerProvider({ contactId, ...props }) {
// Handle fetch contact duplicate details.
const { data: contact, isLoading: isContactLoading } = useContact(contactId, {
enabled: !!contactId,
});
//provider.
const provider = {
contact,
contactId,
};
return (
<DashboardInsider loading={isContactLoading}>
<DrawerHeaderContent
name="contact-detail-drawer"
title={contact?.display_name}
/>
<ContactDetailDrawerContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useContactDetailDrawerContext = () =>
React.useContext(ContactDetailDrawerContext);
export { ContactDetailDrawerProvider, useContactDetailDrawerContext };

View File

@@ -0,0 +1,49 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Money } from '@/components';
import { useContactDetailDrawerContext } from './ContactDetailDrawerProvider';
import { DetailItem } from '@/components/Details';
export default function ContactDetailList({}) {
const { contact } = useContactDetailDrawerContext();
return (
<div className="details-menu">
<div className="details-menu--vertical">
<DetailItem
label={intl.get('display_name')}
children={contact.display_name}
/>
<DetailItem
label={intl.get('balance')}
children={
<Money
amount={contact?.balance}
currency={contact?.currency_code}
/>
}
/>
</div>
<div className="details-menu--horizontal">
<DetailItem
label={intl.get('closing_balance')}
children={
<Money
amount={contact.closing_balance}
currency={contact?.currency_code}
/>
}
/>
<DetailItem
label={intl.get('contact_type')}
children={contact.contact_type}
/>
<DetailItem
label={intl.get('email')}
children={contact.email ? contact.email : '--'}
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,31 @@
// @ts-nocheck
import React from 'react';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
import { compose } from '@/utils';
const ContactDetailDrawerContent = React.lazy(() =>
import('./ContactDetailDrawerContent'),
);
/**
* Contact detail drawer.
*/
function ContactDetailDrawer({
name,
// #withDrawer
isOpen,
payload: { contactId },
}) {
return (
<Drawer isOpen={isOpen} name={name} size={'750px'}>
<DrawerSuspense>
<ContactDetailDrawerContent contact={contactId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers())(ContactDetailDrawer);

View File

@@ -0,0 +1,67 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { Tab } from '@blueprintjs/core';
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 '@/constants/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,130 @@
// @ts-nocheck
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 '@/constants/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 });
};
// Handle print credit note.
const handlePrintCreditNote = () => {
openDialog('credit-note-pdf-preview', { 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.View} a={AbilitySubject.CreditNote}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" />}
text={<T id={'print'} />}
onClick={handlePrintCreditNote}
/>
</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,22 @@
// @ts-nocheck
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,95 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import {
useCreditNote,
useRefundCreditNote,
useReconcileCreditNote,
useReconcileCreditNotes,
} from '@/hooks/query';
import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state';
import { DrawerHeaderContent, DrawerLoading } from '@/components';
const CreditNoteDetailDrawerContext = React.createContext();
/**
* Credit note detail drawer provider.
*/
function CreditNoteDetailDrawerProvider({ creditNoteId, ...props }) {
// Features guard.
const { featureCan } = useFeatureCan();
// 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,
})}
subTitle={
featureCan(Features.Branches)
? intl.get('credit_note.drawer.subtitle', {
value: creditNote.branch?.name,
})
: null
}
/>
<CreditNoteDetailDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useCreditNoteDetailDrawerContext = () =>
React.useContext(CreditNoteDetailDrawerContext);
export { CreditNoteDetailDrawerProvider, useCreditNoteDetailDrawerContext };

View File

@@ -0,0 +1,35 @@
// @ts-nocheck
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={'note'} />} children={creditNote.note} />
</If>
<If condition={creditNote.terms_conditions}>
<DetailItem label={<T id={'terms_conditions'} />}>
{creditNote.terms_conditions}
</DetailItem>
</If>
</DetailsMenu>
</CommercialDocFooter>
);
}

View File

@@ -0,0 +1,104 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { defaultTo } from 'lodash';
import {
FormatDate,
T,
Row,
Col,
DetailsMenu,
DetailItem,
ButtonLink,
CommercialDocHeader,
CommercialDocTopHeader,
CustomerDrawerLink,
ExchangeRateDetailItem,
} 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_date')}
>
<FormatDate value={creditNote.formatted_credit_note_date} />
</DetailItem>
<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>
<ExchangeRateDetailItem
exchangeRate={creditNote?.exchange_rate}
toCurrency={creditNote?.currency_code}
/>
</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={<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,23 @@
// @ts-nocheck
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,28 @@
// @ts-nocheck
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,43 @@
// @ts-nocheck
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,45 @@
// @ts-nocheck
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 '@/containers/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,50 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { FormatDateCell } 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,50 @@
// @ts-nocheck
import React from 'react';
import { DataTable, Card } from '@/components';
import { TableStyle } from '@/constants';
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,56 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
import { safeCallback } from '@/utils';
import { Can, FormatDateCell, Icon } from '@/components';
import { CreditNoteAction, AbilitySubject } from '@/constants/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,48 @@
// @ts-nocheck
import React from 'react';
import { DataTable, Card } from '@/components';
import { TableStyle } from '@/constants';
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,64 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
import { Can, FormatDateCell, Icon } from '@/components';
import { safeCallback } from '@/utils';
import { CreditNoteAction, AbilitySubject } from '@/constants/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,34 @@
// @ts-nocheck
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,143 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import {
Button,
Popover,
PopoverInteractionKind,
Position,
MenuItem,
Menu,
Tag,
Intent,
} from '@blueprintjs/core';
import { getColumnWidth } from '@/utils';
import {
Icon,
FormattedMessage as T,
TextOverviewTooltipCell,
FormatNumberCell,
Choose,
} from '@/components';
import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvider';
export const useCreditNoteReadOnlyEntriesColumns = () => {
// credit note details drawer context.
const {
creditNote: { entries },
} = useCreditNoteDetailDrawerContext();
return React.useMemo(
() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
Cell: TextOverviewTooltipCell,
width: 150,
className: 'name',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('description'),
accessor: 'description',
Cell: TextOverviewTooltipCell,
className: 'description',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'quantity', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'rate', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: getColumnWidth(entries, 'amount', {
minWidth: 60,
magicSpacing: 5,
}),
align: 'right',
disableSortBy: true,
textOverview: true,
},
],
[],
);
};
/**
* 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

@@ -0,0 +1,25 @@
// @ts-nocheck
import React from 'react';
import clsx from 'classnames';
import { Card } from '@/components';
import CustomerDetailsActionsBar from './CustomerDetailsActionsBar';
import CustomerDetailsHeader from './CustomerDetailsHeader';
import Style from './CustomerDetailsDrawer.module.scss';
/**
* contact detail.
*/
export default function CustomerDetails() {
return (
<div className={clsx(Style.root)}>
<CustomerDetailsActionsBar />
<Card>
<CustomerDetailsHeader />
</Card>
</div>
);
}

View File

@@ -0,0 +1,173 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
Position,
PopoverInteractionKind,
Popover,
Menu,
MenuItem,
} from '@blueprintjs/core';
import { useCustomerDetailsDrawerContext } from './CustomerDetailsDrawerProvider';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import {
DashboardActionsBar,
Can,
Icon,
FormattedMessage as T,
} from '@/components';
import { CustomerMoreMenuItem } from './utils';
import {
AbilitySubject,
SaleInvoiceAction,
SaleEstimateAction,
SaleReceiptAction,
PaymentReceiveAction,
CustomerAction,
} from '@/constants/abilityOption';
import { compose } from '@/utils';
/**
* Customer details actions bar.
*/
function CustomerDetailsActionsBar({
// #withDialogActions
openDialog,
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const { contact, customerId } = useCustomerDetailsDrawerContext();
const history = useHistory();
// Handle new invoice button click.
const handleNewInvoiceClick = () => {
history.push('/invoices/new');
closeDrawer('customer-detail-drawer');
};
// Handle new receipt button click.
const handleNewReceiptClick = () => {
history.push('/receipts/new');
closeDrawer('customer-detail-drawer');
};
// Handle new payment receive button click.
const handleNewPaymentClick = () => {
history.push('/payment-receives/new');
closeDrawer('customer-detail-drawer');
};
// Handle new estimate button click.
const handleNewEstimateClick = () => {
history.push('/estimates/new');
closeDrawer('customer-detail-drawer');
};
// Handles delete customer click.
const handleDeleteCustomer = () => {
openAlert(`customer-delete`, { contactId: customerId });
};
// Handles edit customer click.
const handleEditContact = () => {
history.push(`/customers/${customerId}/edit`);
closeDrawer('customer-details-drawer');
};
// Handle edit opening balance click.
const handleEditOpeningBalance = () => {
openDialog('customer-opening-balance', { customerId });
};
return (
<DashboardActionsBar>
<NavbarGroup>
<Popover
content={
<Menu>
<Can I={SaleInvoiceAction.Create} a={AbilitySubject.Invoice}>
<MenuItem
text={<T id={'customer.drawer.action.new_invoice'} />}
onClick={handleNewInvoiceClick}
/>
</Can>
<Can I={SaleEstimateAction.Create} a={AbilitySubject.Estimate}>
<MenuItem
text={<T id={'customer.drawer.action.new_estimate'} />}
onClick={handleNewEstimateClick}
/>
</Can>
<Can I={SaleReceiptAction.Create} a={AbilitySubject.Receipt}>
<MenuItem
text={<T id={'customer.drawer.action.new_receipt'} />}
onClick={handleNewReceiptClick}
/>
</Can>
<Can
I={PaymentReceiveAction.Create}
a={AbilitySubject.PaymentReceive}
>
<MenuItem
text={<T id={'customer.drawer.action.new_payment'} />}
onClick={handleNewPaymentClick}
/>
</Can>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={clsx(Classes.MINIMAL)}
text={<T id={'customer.drawer.action.new_transaction'} />}
icon={<Icon icon={'plus'} />}
/>
</Popover>
<Can I={CustomerAction.Edit} a={AbilitySubject.Customer}>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={intl.get('customer.drawer.action.edit')}
onClick={handleEditContact}
/>
</Can>
<Can I={CustomerAction.Delete} a={AbilitySubject.Customer}>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeleteCustomer}
/>
</Can>
<NavbarDivider />
<CustomerMoreMenuItem
payload={{
onEditOpeningBalance: handleEditOpeningBalance,
}}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withDrawerActions,
withAlertsActions,
withDialogActions,
)(CustomerDetailsActionsBar);

View File

@@ -0,0 +1,15 @@
.root {
&_content {
&_primary {
padding-bottom: 15px;
border-bottom: 1px solid #e2e2e2;
}
}
:global .card {
margin: 15px;
padding: 18px;
}
}

View File

@@ -0,0 +1,22 @@
// @ts-nocheck
import React from 'react';
import { DrawerBody } from '@/components';
import { CustomerDetailsDrawerProvider } from './CustomerDetailsDrawerProvider';
import CustomerDetails from './CustomerDetails';
/**
* Contact detail drawer content.
*/
export default function CustomerDetailsDrawerContent({
// #ownProp
customerId,
}) {
return (
<CustomerDetailsDrawerProvider customerId={customerId}>
<DrawerBody>
<CustomerDetails />
</DrawerBody>
</CustomerDetailsDrawerProvider>
);
}

View File

@@ -0,0 +1,40 @@
// @ts-nocheck
import React from 'react';
import { useCustomer } from '@/hooks/query';
import { DrawerHeaderContent, DrawerLoading } from '@/components';
const ContactDetailDrawerContext = React.createContext();
/**
* Contact detail provider.
*/
function CustomerDetailsDrawerProvider({ customerId, ...props }) {
// Handle fetch customer details.
const { data: customer, isLoading: isCustomerLoading } = useCustomer(
customerId,
{
enabled: !!customerId,
},
);
// Provider.
const provider = {
customer,
customerId,
isCustomerLoading,
};
return (
<DrawerLoading loading={isCustomerLoading}>
<DrawerHeaderContent
name="customer-detail-drawer"
title={customer?.display_name}
/>
<ContactDetailDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useCustomerDetailsDrawerContext = () =>
React.useContext(ContactDetailDrawerContext);
export { CustomerDetailsDrawerProvider, useCustomerDetailsDrawerContext };

View File

@@ -0,0 +1,85 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import { defaultTo } from 'lodash';
import { DetailsMenu, DetailItem, T } from '@/components';
import { useCustomerDetailsDrawerContext } from './CustomerDetailsDrawerProvider';
import Style from './CustomerDetailsDrawer.module.scss';
/**
* Customer details header.
*/
export default function CustomerDetailsHeader() {
const { customer } = useCustomerDetailsDrawerContext();
return (
<div className={clsx(Style.root_content)}>
<DetailsMenu
direction={'vertical'}
className={clsx(Style.root_content_primary)}
>
<DetailItem
name={'outstanding-receivable'}
label={<T id={'customer.drawer.label.outstanding_receivable'} />}
>
<h3 class="big-number">{customer.formatted_balance}</h3>
</DetailItem>
<DetailItem
label={<T id={'customer.drawer.label.customer_type'} />}
name={'type'}
children={customer?.formatted_customer_type }
/>
<DetailItem label={<T id={'customer.drawer.label.unused_credits'} />}>
0
</DetailItem>
</DetailsMenu>
<DetailsMenu direction={'horizantal'} minLabelSize={'175px'}>
<DetailItem
label={<T id={'customer.drawer.label.customer_name'} />}
name={'name'}
>
<strong>{customer?.display_name}</strong>
</DetailItem>
<DetailItem
label={<T id={'customer.drawer.label.company_name'} />}
children={defaultTo(customer?.company_name, '--')}
/>
<DetailItem
label={<T id={'customer.drawer.label.email'} />}
children={defaultTo(customer?.email, '--')}
/>
<DetailItem label={<T id={'customer.drawer.label.phone_number'} />}>
<div>{customer?.personal_phone} </div>
<div>{customer?.work_phone} </div>
</DetailItem>
<DetailItem
label={<T id={'customer.drawer.label.website'} />}
children={defaultTo(customer?.website, '--')}
/>
<DetailItem
label={<T id={'customer.drawer.label.opening_balance'} />}
children={customer?.formatted_opening_balance}
/>
<DetailItem
label={<T id={'customer.drawer.label.opening_balance_at'} />}
children={customer?.formatted_opening_balance_at}
/>
<DetailItem
label={<T id={'customer.drawer.label.currency'} />}
children={customer?.currency_code}
/>
<DetailItem
label={<T id={'customer.drawer.label.note'} />}
children={defaultTo(customer?.note, '--')}
/>
</DetailsMenu>
</div>
);
}

View File

@@ -0,0 +1,31 @@
// @ts-nocheck
import React from 'react';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
import { compose } from '@/utils';
const CustomerDetailsDrawerContent = React.lazy(() =>
import('./CustomerDetailsDrawerContent'),
);
/**
* Contact detail drawer.
*/
function CustomerDetailsDrawer({
name,
// #withDrawer
isOpen,
payload: { customerId },
}) {
return (
<Drawer isOpen={isOpen} name={name} size={'750px'}>
<DrawerSuspense>
<CustomerDetailsDrawerContent customerId={customerId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers())(CustomerDetailsDrawer);

View File

@@ -0,0 +1,38 @@
// @ts-nocheck
import React from 'react';
import {
Button,
Popover,
PopoverInteractionKind,
Position,
MenuItem,
Menu,
} from '@blueprintjs/core';
import { Icon, FormattedMessage as T } from '@/components';
/**
* Customer more actions menu items.
* @returns
*/
export function CustomerMoreMenuItem({ payload: { onEditOpeningBalance } }) {
return (
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={
<Menu>
<MenuItem
text={<T id={'customer.drawer.action.edit_opening_balance'} />}
onClick={onEditOpeningBalance}
/>
</Menu>
}
>
<Button icon={<Icon icon="more-vert" iconSize={16} />} minimal={true} />
</Popover>
);
}

View File

@@ -0,0 +1,40 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import intl from 'react-intl-universal';
import { Tab } from '@blueprintjs/core';
import { DrawerMainTabs } from '@/components';
import EstimateDetailActionsBar from './EstimateDetailActionsBar';
import EstimateDetailPanel from './EstimateDetailPanel';
/**
* 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 (
<EstimateDetailsRoot>
<EstimateDetailActionsBar />
<EstimateDetailsTabs />
</EstimateDetailsRoot>
);
}
const EstimateDetailsRoot = styled.div``;

View File

@@ -0,0 +1,114 @@
// @ts-nocheck
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { SaleEstimateAction, AbilitySubject } from '@/constants/abilityOption';
import { EstimateMoreMenuItems } from './components';
import {
DrawerActionsBar,
Icon,
FormattedMessage as T,
Can,
} from '@/components';
import { compose } from '@/utils';
/**
* Estimate read-only details actions bar of the drawer.
*/
function EstimateDetailActionsBar({
// #withDialogActions
openDialog,
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
// Estimate details drawer context.
const { estimateId } = useEstimateDetailDrawerContext();
// History.
const history = useHistory();
// Handle edit sale estimate.
const handleEditEstimate = () => {
history.push(`/estimates/${estimateId}/edit`);
closeDrawer('estimate-detail-drawer');
};
// Handle delete sale estimate.
const handleDeleteEstimate = () => {
openAlert('estimate-delete', { estimateId });
};
// Handle print estimate.
const handlePrintEstimate = () => {
openDialog('estimate-pdf-preview', { estimateId });
};
// Handle notify via SMS.
const handleNotifyViaSMS = () => {
openDialog('notify-estimate-via-sms', { estimateId });
};
return (
<DrawerActionsBar>
<NavbarGroup>
<Can I={SaleEstimateAction.Edit} a={AbilitySubject.Estimate}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'edit_estimate'} />}
onClick={handleEditEstimate}
/>
<NavbarDivider />
</Can>
<Can I={SaleEstimateAction.View} a={AbilitySubject.Estimate}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" />}
text={<T id={'print'} />}
onClick={handlePrintEstimate}
/>
</Can>
<Can I={SaleEstimateAction.Delete} a={AbilitySubject.Estimate}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeleteEstimate}
/>
</Can>
<Can I={SaleEstimateAction.NotifyBySms} a={AbilitySubject.Estimate}>
<NavbarDivider />
<EstimateMoreMenuItems
payload={{
onNotifyViaSMS: handleNotifyViaSMS,
}}
/>
</Can>
</NavbarGroup>
</DrawerActionsBar>
);
}
export default compose(
withDialogActions,
withAlertsActions,
withDrawerActions,
)(EstimateDetailActionsBar);

View File

@@ -0,0 +1,22 @@
// @ts-nocheck
import React from 'react';
import { DrawerBody } from '@/components';
import EstimateDetail from './EstimateDetail';
import { EstimateDetailDrawerProvider } from './EstimateDetailDrawerProvider';
/**
* Estimate detail drawer content.
*/
export default function EstimateDetailDrawerContent({
// #ownProp
estimateId,
}) {
return (
<EstimateDetailDrawerProvider estimateId={estimateId}>
<DrawerBody>
<EstimateDetail />
</DrawerBody>
</EstimateDetailDrawerProvider>
);
}

View File

@@ -0,0 +1,52 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Features } from '@/constants';
import { useEstimate } from '@/hooks/query';
import { useFeatureCan } from '@/hooks/state';
import { DrawerHeaderContent, DrawerLoading } from '@/components';
const EstimateDetailDrawerContext = React.createContext();
/**
* Estimate detail provider.
*/
function EstimateDetailDrawerProvider({ estimateId, ...props }) {
// Features guard.
const { featureCan } = useFeatureCan();
// Fetches the estimate by the given id.
const { data: estimate, isLoading: isEstimateLoading } = useEstimate(
estimateId,
{ enabled: !!estimateId },
);
const provider = {
estimateId,
estimate,
};
return (
<DrawerLoading loading={isEstimateLoading}>
<DrawerHeaderContent
name="estimate-detail-drawer"
title={intl.get('estimate.drawer.title', {
number: estimate.estimate_number,
})}
subTitle={
featureCan(Features.Branches)
? intl.get('estimate.drawer.subtitle', {
value: estimate.branch?.name,
})
: null
}
/>
<EstimateDetailDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useEstimateDetailDrawerContext = () =>
React.useContext(EstimateDetailDrawerContext);
export { EstimateDetailDrawerProvider, useEstimateDetailDrawerContext };

View File

@@ -0,0 +1,36 @@
// @ts-nocheck
import React from 'react';
import {
CommercialDocFooter,
T,
If,
DetailsMenu,
DetailItem,
} from '@/components';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
/**
* Estimate details footer.
* @returns {React.JSX}
*/
export default function EstimateDetailFooter() {
const { estimate } = useEstimateDetailDrawerContext();
return (
<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

@@ -0,0 +1,99 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { defaultTo } from 'lodash';
import {
CommercialDocHeader,
CommercialDocTopHeader,
FormatDate,
T,
DetailsMenu,
DetailItem,
Row,
Col,
CustomerDrawerLink,
ExchangeRateDetailItem,
} from '@/components';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
import { EstimateDetailsStatus } from './components';
/**
* Estimate read-only details drawer header.
*/
export default function EstimateDetailHeader() {
const { estimate } = useEstimateDetailDrawerContext();
return (
<CommercialDocHeader>
<CommercialDocTopHeader>
<DetailsMenu>
<AmountEstimateDetail label={intl.get('amount')}>
<span class="big-number">{estimate.formatted_amount}</span>
</AmountEstimateDetail>
<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}
/>
<ExchangeRateDetailItem
exchangeRate={estimate?.exchange_rate}
toCurrency={estimate?.currency_code}
/>
</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

@@ -0,0 +1,21 @@
// @ts-nocheck
import React from 'react';
import { CommercialDocBox } from '@/components';
import EstimateDetailHeader from './EstimateDetailHeader';
import EstimateDetailTable from './EstimateDetailTable';
import EstimateDetailTableFooter from './EstimateDetailTableFooter';
import EstimateDetailFooter from './EstimateDetailFooter';
export default function EstimateDetailTab() {
return (
<CommercialDocBox>
<EstimateDetailHeader />
<EstimateDetailTable />
<EstimateDetailTableFooter />
<EstimateDetailFooter />
</CommercialDocBox>
);
}

View File

@@ -0,0 +1,29 @@
// @ts-nocheck
import React from 'react';
import { CommercialDocEntriesTable } from '@/components';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
import { useEstimateReadonlyEntriesColumns } from './utils';
import { TableStyle } from '@/constants';
/**
* Estimate detail table.
*/
export default function EstimateDetailTable() {
const {
estimate: { entries },
} = useEstimateDetailDrawerContext();
// Estimate entries table columns.
const columns = useEstimateReadonlyEntriesColumns();
return (
<CommercialDocEntriesTable
columns={columns}
data={entries}
styleName={TableStyle.Constrant}
/>
);
}

View File

@@ -0,0 +1,44 @@
// @ts-nocheck
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,33 @@
// @ts-nocheck
const globalStateClassesMapping = {
active: 'active',
checked: 'checked',
completed: 'completed',
disabled: 'disabled',
error: 'error',
expanded: 'expanded',
focused: 'focused',
focusVisible: 'focusVisible',
required: 'required',
selected: 'selected',
};
function generateUtilityClass(componentName, slot) {
const globalStateClass = globalStateClassesMapping[slot];
return globalStateClass || `${componentName}__${slot}`;
}
function generateUtilityClasses(componentName, modifiers) {
const result = {
root: componentName,
};
modifiers.forEach((modifier) => {
result[modifier] = generateUtilityClass(componentName, modifier);
});
return result;
}
export const EstimateDrawerCls = generateUtilityClasses('estimate-drawer', [
'content',
]);

View File

@@ -0,0 +1,73 @@
// @ts-nocheck
import React from 'react';
import {
Intent,
Button,
Popover,
PopoverInteractionKind,
Position,
MenuItem,
Menu,
Tag,
} from '@blueprintjs/core';
import { Icon, 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>
);
}
export function EstimateMoreMenuItems({ payload: { onNotifyViaSMS } }) {
return (
<Popover
minimal={true}
content={
<Menu>
<MenuItem
onClick={onNotifyViaSMS}
text={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
/>
</Menu>
}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
modifiers={{
offset: { offset: '0, 4' },
}}
>
<Button icon={<Icon icon="more-vert" iconSize={16} />} minimal={true} />
</Popover>
);
}

View File

@@ -0,0 +1,33 @@
// @ts-nocheck
import React from 'react';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
import { compose } from '@/utils';
const EstimateDetailDrawerContent = React.lazy(() =>
import('./EstimateDetailDrawerContent'),
);
function EstimateDetailDrawer({
name,
// #withDrawer
isOpen,
payload: { estimateId },
}) {
return (
<Drawer
isOpen={isOpen}
name={name}
style={{ minWidth: '700px', maxWidth: '900px' }}
size={'65%'}
>
<DrawerSuspense>
<EstimateDetailDrawerContent estimateId={estimateId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers())(EstimateDetailDrawer);

View File

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

View File

@@ -0,0 +1,80 @@
// @ts-nocheck
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Button,
Classes,
NavbarGroup,
Intent,
NavbarDivider,
} from '@blueprintjs/core';
import {
Icon,
DrawerActionsBar,
Can,
FormattedMessage as T,
} from '@/components';
import { ExpenseAction, AbilitySubject } from '@/constants/abilityOption';
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { compose } from '@/utils';
/**
* Expense drawer action bar.
*/
function ExpenseDrawerActionBar({
// #withAlertsDialog
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
// Expense drawer context.
const { expense } = useExpenseDrawerContext();
// Handle the expense edit action.
const handleEditExpense = () => {
history.push(`/expenses/${expense.id}/edit`);
closeDrawer('expense-drawer');
};
// Handle the expense delete action.
const handleDeleteExpense = () => {
openAlert('expense-delete', { expenseId: expense.id });
};
return (
<DrawerActionsBar>
<NavbarGroup>
<Can I={ExpenseAction.Edit} a={AbilitySubject.Expense}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'edit_expense'} />}
onClick={handleEditExpense}
/>
</Can>
<Can I={ExpenseAction.Delete} a={AbilitySubject.Expense}>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon="trash-16" iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeleteExpense}
/>
</Can>
</NavbarGroup>
</DrawerActionsBar>
);
}
export default compose(
withAlertsActions,
withDrawerActions,
)(ExpenseDrawerActionBar);

View File

@@ -0,0 +1,23 @@
// @ts-nocheck
import React from 'react';
import { DrawerBody } from '@/components';
import { ExpenseDrawerProvider } from './ExpenseDrawerProvider';
import ExpenseDrawerDetails from './ExpenseDrawerDetails';
/**
* Expense drawer content.
*/
export default function ExpenseDrawerContent({
// #ownProp
expenseId,
}) {
return (
<ExpenseDrawerProvider expenseId={expenseId}>
<DrawerBody>
<ExpenseDrawerDetails />
</DrawerBody>
</ExpenseDrawerProvider>
);
}

View File

@@ -0,0 +1,29 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import { CommercialDocBox } from '@/components';
import ExpenseDrawerActionBar from './ExpenseDrawerActionBar';
import ExpenseDrawerHeader from './ExpenseDrawerHeader';
import ExpenseDrawerTable from './ExpenseDrawerTable';
import ExpenseDrawerFooter from './ExpenseDrawerFooter';
/**
* Expense view details.
*/
export default function ExpenseDrawerDetails() {
return (
<ExpenseDetailsRoot>
<ExpenseDrawerActionBar />
<CommercialDocBox>
<ExpenseDrawerHeader />
<ExpenseDrawerTable />
<ExpenseDrawerFooter />
</CommercialDocBox>
</ExpenseDetailsRoot>
);
}
const ExpenseDetailsRoot = styled.div``;

View File

@@ -0,0 +1,43 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import {
T,
TotalLines,
TotalLineBorderStyle,
TotalLineTextStyle,
} from '@/components';
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
import { FormatNumber, TotalLine } from '@/components';
/**
* Footer details of expense readonly details.
*/
export default function ExpenseDrawerFooter() {
const { expense } = useExpenseDrawerContext();
return (
<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

@@ -0,0 +1,87 @@
// @ts-nocheck
import React from 'react';
import moment from 'moment';
import styled from 'styled-components';
import { defaultTo } from 'lodash';
import {
CommercialDocHeader,
CommercialDocTopHeader,
Row,
Col,
DetailItem,
DetailsMenu,
FormatDate,
ExchangeRateDetailItem,
FormattedMessage as T,
} from '@/components';
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
import { ExpenseDetailsStatus } from './components';
/**
* Expense drawer content.
*/
export default function ExpenseDrawerHeader() {
const { expense } = useExpenseDrawerContext();
return (
<CommercialDocHeader>
<CommercialDocTopHeader>
<DetailsMenu>
<DetailItem name={'amount'} label={<T id={'full_amount'} />}>
<h3 class="big-number">{expense.formatted_amount}</h3>
</DetailItem>
<StatusDetailItem>
<ExpenseDetailsStatus expense={expense} />
</StatusDetailItem>
</DetailsMenu>
</CommercialDocTopHeader>
<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(expense.reference_no, '-')}
</DetailItem>
<DetailItem label={<T id={'description'} />}>
{defaultTo(expense.description, '—')}
</DetailItem>
<ExchangeRateDetailItem
exchangeRate={expense?.exchange_rate}
toCurrency={expense?.currency_code}
/>
</DetailsMenu>
</Col>
<Col xs={6}>
<DetailsMenu
textAlign={'right'}
direction={'horizantal'}
minLabelSize={'180px'}
>
<DetailItem label={<T id={'published_at'} />}>
<FormatDate value={expense.published_at} />
</DetailItem>
<DetailItem label={<T id={'created_at'} />}>
<FormatDate value={expense.created_at} />
</DetailItem>
</DetailsMenu>
</Col>
</Row>
</CommercialDocHeader>
);
}
const StatusDetailItem = styled(DetailItem)`
width: 50%;
text-align: right;
position: relative;
top: -5px;
`;

View File

@@ -0,0 +1,56 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { useExpense } from '@/hooks/query';
import { DrawerHeaderContent, DrawerLoading } from '@/components';
import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state';
const ExpenseDrawerDrawerContext = React.createContext();
/**
* Expense drawer provider.
*/
function ExpenseDrawerProvider({ expenseId, ...props }) {
// Features guard.
const { featureCan } = useFeatureCan();
// Fetch the expense details.
const {
data: expense,
isLoading: isExpenseLoading,
isFetching: isExpenseFetching,
} = useExpense(expenseId, {
enabled: !!expenseId,
});
// Provider.
const provider = {
expenseId,
expense,
isExpenseFetching,
isExpenseLoading,
};
return (
<DrawerLoading loading={isExpenseLoading}>
<DrawerHeaderContent
name="expense-drawer"
title={intl.get('expense.drawer.title')}
subTitle={
featureCan(Features.Branches)
? intl.get('expense.drawer.subtitle', {
value: expense.branch?.name,
})
: null
}
/>
<ExpenseDrawerDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useExpenseDrawerContext = () =>
React.useContext(ExpenseDrawerDrawerContext);
export { ExpenseDrawerProvider, useExpenseDrawerContext };

View File

@@ -0,0 +1,28 @@
// @ts-nocheck
import React from 'react';
import { CommercialDocEntriesTable } from '@/components';
import { useExpenseReadEntriesColumns } from './utils';
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
import { TableStyle } from '@/constants';
/**
* Expense details table.
*/
export default function ExpenseDrawerTable() {
// Expense readonly entries columns.
const columns = useExpenseReadEntriesColumns();
// Expense drawer context.
const { expense } = useExpenseDrawerContext();
return (
<CommercialDocEntriesTable
columns={columns}
data={expense.categories}
styleName={TableStyle.Constrant}
/>
);
}

View File

@@ -0,0 +1,21 @@
// @ts-nocheck
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

@@ -0,0 +1,34 @@
// @ts-nocheck
import React, { lazy } from 'react';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
import { compose } from '@/utils';
const ExpenseDrawerContent = lazy(() => import('./ExpenseDrawerContent'));
/**
* Expense drawer.
*/
function ExpenseDrawer({
name,
// #withDrawer
isOpen,
payload: { expenseId },
}) {
return (
<Drawer
isOpen={isOpen}
name={name}
size={'65%'}
style={{ minWidth: '700px', maxWidth: '900px' }}
>
<DrawerSuspense>
<ExpenseDrawerContent expenseId={expenseId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers())(ExpenseDrawer);

View File

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

View File

@@ -0,0 +1,49 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { Tab } from '@blueprintjs/core';
import { DrawerMainTabs } from '@/components';
import InventoryAdjustmentDetailTab from './InventoryAdjustmentDetailTab';
import InventoryAdjustmentDetailActionsBar from './InventoryAdjustmentDetailActionsBar';
import InventoryAdjustmentDetailGLEntriesPanel from './InventoryAdjustmentDetailGLEntriesPanel';
/**
* Inventory adjustment detail
* @returns {React.JSX}
*/
export default function InventoryAdjustmentDetail() {
return (
<InventoryAdjustmentDetailsRoot>
<InventoryAdjustmentDetailActionsBar />
<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

@@ -0,0 +1,56 @@
// @ts-nocheck
import React from 'react';
import { Button, NavbarGroup, Classes, Intent } from '@blueprintjs/core';
import { useInventoryAdjustmentDrawerContext } from './InventoryAdjustmentDrawerProvider';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import {
Icon,
DrawerActionsBar,
FormattedMessage as T,
Can,
} from '@/components';
import {
InventoryAdjustmentAction,
AbilitySubject,
} from '@/constants/abilityOption';
import { compose } from '@/utils';
/**
* Inventory adjustment detail actions bar.
*/
function InventoryAdjustmentDetailActionsBar({
// #withAlertsActions
openAlert,
}) {
const { inventoryId } = useInventoryAdjustmentDrawerContext();
// Handle delete inventory adjustment.
const handleDeleteInventoryAdjustment = () => {
openAlert('inventory-adjustment-delete', { inventoryId });
};
return (
<Can
I={InventoryAdjustmentAction.Delete}
a={AbilitySubject.InventoryAdjustment}
>
<DrawerActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeleteInventoryAdjustment}
/>
</NavbarGroup>
</DrawerActionsBar>
</Can>
);
}
export default compose(withAlertsActions)(InventoryAdjustmentDetailActionsBar);

View File

@@ -0,0 +1,43 @@
// @ts-nocheck
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

@@ -0,0 +1,52 @@
// @ts-nocheck
import React from 'react';
import moment from 'moment';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import { defaultTo } from 'lodash';
import { DetailsMenu, DetailItem, FormatDate } from '@/components';
import { useInventoryAdjustmentDrawerContext } from './InventoryAdjustmentDrawerProvider';
import InventoryAdjustmentDrawerCls from '@/style/components/Drawers/InventoryAdjustmentDrawer.module.scss';
/**
* Inventory detail header.
*/
export default function InventoryAdjustmentDetailHeader() {
const { inventoryAdjustment } = useInventoryAdjustmentDrawerContext();
return (
<div className={clsx(InventoryAdjustmentDrawerCls.detail_panel_header)}>
<DetailsMenu direction={'horizantal'} minLabelSize={'180px'}>
<DetailItem label={intl.get('date')}>
<FormatDate value={inventoryAdjustment.date} />
</DetailItem>
<DetailItem label={intl.get('type')}>
{inventoryAdjustment.formatted_type}
</DetailItem>
<DetailItem label={intl.get('adjustment_account')}>
{inventoryAdjustment.adjustment_account.name}
</DetailItem>
<DetailItem name={'reference'} label={intl.get('reference_no')}>
{defaultTo(inventoryAdjustment.reference_no, '-')}
</DetailItem>
<DetailItem label={intl.get('published_at')}>
<FormatDate value={inventoryAdjustment.published_at} />
</DetailItem>
<DetailItem label={intl.get('reason')}>
{defaultTo(inventoryAdjustment.reason, '—')}
</DetailItem>
<DetailItem label={intl.get('created_at')}>
<FormatDate value={inventoryAdjustment.created_at} />
</DetailItem>
</DetailsMenu>
</div>
);
}

View File

@@ -0,0 +1,17 @@
// @ts-nocheck
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

@@ -0,0 +1,25 @@
// @ts-nocheck
import React from 'react';
import { CommercialDocEntriesTable } from '@/components';
import { useInventoryAdjustmentEntriesColumns } from './utils';
import { useInventoryAdjustmentDrawerContext } from './InventoryAdjustmentDrawerProvider';
/**
* Inventory adjustment detail entries table.
*/
export default function InventoryAdjustmentDetailTable() {
// Inventory adjustment entries columns.
const columns = useInventoryAdjustmentEntriesColumns();
// Inventory adjustment details drawer context.
const { inventoryAdjustment } = useInventoryAdjustmentDrawerContext();
return (
<CommercialDocEntriesTable
columns={columns}
data={inventoryAdjustment.entries}
className={'table-constrant'}
/>
);
}

View File

@@ -0,0 +1,19 @@
// @ts-nocheck
import React from 'react';
import { DrawerBody } from '@/components';
import { InventoryAdjustmentDrawerProvider } from './InventoryAdjustmentDrawerProvider';
import InventoryAdjustmentDetail from './InventoryAdjustmentDetail';
/**
* Inventory adjustment drawer content.
*/
export default function InventoryAdjustmentDrawerContent({ inventoryId }) {
return (
<InventoryAdjustmentDrawerProvider inventoryId={inventoryId}>
<DrawerBody>
<InventoryAdjustmentDetail />
</DrawerBody>
</InventoryAdjustmentDrawerProvider>
);
}

View File

@@ -0,0 +1,42 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { DrawerHeaderContent, DrawerLoading } from '@/components';
import { useInventoryAdjustment } from '@/hooks/query';
const InventoryAdjustmentDrawerContext = React.createContext();
/**
* Inventory adjustment drawer provider.
*/
function InventoryAdjustmentDrawerProvider({ inventoryId, ...props }) {
// Handle fetch inventory adjustment .
const { data: inventoryAdjustment, isLoading: isAdjustmentsLoading } =
useInventoryAdjustment(inventoryId, {
enabled: !!inventoryId,
});
//provider.
const provider = {
inventoryAdjustment,
inventoryId,
};
return (
<DrawerLoading loading={isAdjustmentsLoading}>
<DrawerHeaderContent
name="inventory-adjustment-drawer"
title={intl.get('inventory_adjustment.details_drawer.title')}
/>
<InventoryAdjustmentDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useInventoryAdjustmentDrawerContext = () =>
React.useContext(InventoryAdjustmentDrawerContext);
export {
InventoryAdjustmentDrawerProvider,
useInventoryAdjustmentDrawerContext,
};

View File

@@ -0,0 +1,36 @@
// @ts-nocheck
import React from 'react';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
import { compose } from '@/utils';
const InventoryAdjustmentDrawerContent = React.lazy(() =>
import('./InventoryAdjustmentDrawerContent'),
);
/**
* Inventory adjustment detail drawer.
*/
function InventoryAdjustmentDetailDrawer({
name,
// #withDrawer
isOpen,
payload: { inventoryId },
}) {
return (
<Drawer
isOpen={isOpen}
name={name}
style={{ minWidth: '700px', maxWidth: '900px' }}
size={'65%'}
>
<DrawerSuspense>
<InventoryAdjustmentDrawerContent inventoryId={inventoryId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers())(InventoryAdjustmentDetailDrawer);

View File

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

View File

@@ -0,0 +1,64 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import intl from 'react-intl-universal';
import { Tab } from '@blueprintjs/core';
import { useAbilityContext } from '@/hooks/utils';
import { DrawerMainTabs } from '@/components';
import { PaymentReceiveAction, AbilitySubject } from '@/constants/abilityOption';
import InvoiceDetailActionsBar from './InvoiceDetailActionsBar';
import InvoiceGLEntriesTable from './InvoiceGLEntriesTable';
import InvoicePaymentTransactionsTable from './InvoicePaymentTransactions/InvoicePaymentTransactionsTable';
import InvoiceDetailTab from './InvoiceDetailTab';
/**
* 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() {
return (
<InvoiceDetailsRoot>
<InvoiceDetailActionsBar />
<InvoiceDetailsTabs />
</InvoiceDetailsRoot>
);
}
export const InvoiceDetailsRoot = styled.div``;

View File

@@ -0,0 +1,155 @@
// @ts-nocheck
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import {
If,
Can,
Icon,
DrawerActionsBar,
FormattedMessage as T,
} from '@/components';
import {
SaleInvoiceAction,
PaymentReceiveAction,
AbilitySubject,
} from '../../../constants/abilityOption';
import { compose } from '@/utils';
import { BadDebtMenuItem } from './utils';
/**
* Invoice details action bar.
*/
function InvoiceDetailActionsBar({
// #withDialogActions
openDialog,
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
// Invoice detail drawer context.
const { invoiceId, invoice } = useInvoiceDetailDrawerContext();
// Handle edit sale invoice.
const handleEditInvoice = () => {
history.push(`/invoices/${invoiceId}/edit`);
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 });
};
// Handle print invoices.
const handlePrintInvoice = () => {
openDialog('invoice-pdf-preview', { invoiceId });
};
// Handle quick payment invoice.
const handleQuickPaymentInvoice = () => {
openDialog('quick-payment-receive', { invoiceId });
};
// Handle write-off invoice.
const handleBadDebtInvoice = () => {
openDialog('write-off-bad-debt', { invoiceId });
};
// Handle notify via SMS.
const handleNotifyViaSMS = () => {
openDialog('notify-invoice-via-sms', { invoiceId });
};
// Handle cancele write-off invoice.
const handleCancelBadDebtInvoice = () => {
openAlert('cancel-bad-debt', { invoiceId });
};
return (
<DrawerActionsBar>
<NavbarGroup>
<Can I={SaleInvoiceAction.Edit} a={AbilitySubject.Invoice}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
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="arrow-downward" iconSize={18} />}
text={<T id={'add_payment'} />}
onClick={handleQuickPaymentInvoice}
/>
</If>
<NavbarDivider />
</Can>
<Can I={SaleInvoiceAction.View} a={AbilitySubject.Invoice}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" />}
text={<T id={'print'} />}
onClick={handlePrintInvoice}
/>
</Can>
<Can I={SaleInvoiceAction.Delete} a={AbilitySubject.Invoice}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeleteInvoice}
/>
</Can>
<Can I={SaleInvoiceAction.Writeoff} a={AbilitySubject.Invoice}>
<NavbarDivider />
<BadDebtMenuItem
payload={{
onBadDebt: handleBadDebtInvoice,
onCancelBadDebt: handleCancelBadDebtInvoice,
onNotifyViaSMS: handleNotifyViaSMS,
onConvert: handleConvertToCreitNote,
}}
/>
</Can>
</NavbarGroup>
</DrawerActionsBar>
);
}
export default compose(
withDialogActions,
withDrawerActions,
withAlertsActions,
)(InvoiceDetailActionsBar);

View File

@@ -0,0 +1,23 @@
// @ts-nocheck
import React from 'react';
import { DrawerBody } from '@/components';
import InvoiceDetail from './InvoiceDetail';
import { InvoiceDetailDrawerProvider } from './InvoiceDetailDrawerProvider';
/**
* Invoice detail drawer content.
* @returns {React.JSX}
*/
export default function InvoiceDetailDrawerContent({
// #ownProp
invoiceId,
}) {
return (
<InvoiceDetailDrawerProvider invoiceId={invoiceId}>
<DrawerBody>
<InvoiceDetail />
</DrawerBody>
</InvoiceDetailDrawerProvider>
);
}

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