chrone: sperate client and server to different repos.

This commit is contained in:
a.bouhuolia
2021-09-21 17:13:53 +02:00
parent e011b2a82b
commit 18df5530c7
10015 changed files with 17686 additions and 97524 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,51 @@
import React from 'react';
import { Link } from 'react-router-dom';
import intl from 'react-intl-universal';
import { useAccountDrawerContext } from './AccountDrawerProvider';
import { DataTable, If } from 'components';
import { compose } from 'utils';
import { useAccountReadEntriesColumns } from './utils';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
/**
* account drawer table.
*/
function AccountDrawerTable({ closeDrawer }) {
const {
account,
accounts,
drawerName,
} = useAccountDrawerContext();
// Account read-only entries table columns.
const columns = useAccountReadEntriesColumns();
// Handle view more link click.
const handleLinkClick = () => {
closeDrawer(drawerName);
};
return (
<div className={'account-drawer__table'}>
<DataTable columns={columns} data={accounts} payload={{ account }}/>
<If condition={accounts.length > 0}>
<div class="account-drawer__table-footer">
<Link
to={`/financial-reports/general-ledger`}
onClick={handleLinkClick}
>
{intl.get('view_more_transactions')}
</Link>
</div>
</If>
</div>
);
}
export default compose(withDrawerActions)(AccountDrawerTable);

View File

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

View File

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

View File

@@ -0,0 +1,74 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { useBillDrawerContext } from './BillDrawerProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { Icon, FormattedMessage as T } from 'components';
import { safeCallback, compose } from 'utils';
function BillDetailActionsBar({
// #withDialogActions
openDialog,
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
const { billId } = useBillDrawerContext();
// Handle edit bill.
const onEditBill = () => {
history.push(`/bills/${billId}/edit`);
closeDrawer('bill-drawer');
};
// Handle delete bill.
const onDeleteBill = () => {
openAlert('bill-delete', { billId });
};
return (
<DashboardActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'edit_bill'} />}
onClick={safeCallback(onEditBill)}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={safeCallback(onDeleteBill)}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withDialogActions,
withDrawerActions,
withAlertsActions,
)(BillDetailActionsBar);

View File

@@ -0,0 +1,41 @@
import React from 'react';
import clsx from 'classnames';
import { FormatNumber, T, TotalLines, TotalLine } from '../../../components';
import { useBillDrawerContext } from './BillDrawerProvider';
import BillDrawerCls from 'style/components/Drawers/BillDrawer.module.scss';
/**
* Bill read-only details footer.
*/
export function BillDetailFooter() {
const { bill } = useBillDrawerContext();
return (
<div className={clsx(BillDrawerCls.detail_panel_footer)}>
<TotalLines>
<TotalLine
title={<T id={'bill.details.subtotal'} />}
value={<FormatNumber value={bill.amount} />}
className={BillDrawerCls.total_line_subtotal}
/>
<TotalLine
title={<T id={'bill.details.total'} />}
value={bill.formatted_amount}
className={BillDrawerCls.total_line_total}
/>
<TotalLine
title={<T id={'bill.details.payment_amount'} />}
value={bill.formatted_payment_amount}
className={BillDrawerCls.total_line_payment}
/>
<TotalLine
title={<T id={'bill.details.due_amount'} />}
value={bill.formatted_due_amount}
className={BillDrawerCls.total_line_dueAmount}
/>
</TotalLines>
</div>
);
}

View File

@@ -0,0 +1,58 @@
import React from 'react';
import intl from 'react-intl-universal';
import { defaultTo } from 'lodash';
import clsx from 'classnames';
import { FormatDate, DetailsMenu, DetailItem } from 'components';
import { useBillDrawerContext } from './BillDrawerProvider';
import BillDrawerCls from 'style/components/Drawers/BillDrawer.module.scss';
/**
* Bill detail header.
*/
export default function BillDetailHeader() {
const { bill } = useBillDrawerContext();
return (
<div className={clsx(BillDrawerCls.detail_panel_header)}>
<DetailsMenu>
<DetailItem
label={intl.get('amount')}
children={<h3 class="big-number">{bill.formatted_amount}</h3>}
/>
<DetailItem
label={intl.get('bill.details.bill_number')}
children={defaultTo(bill.bill_number, '-')}
/>
<DetailItem
label={intl.get('bill_date')}
children={<FormatDate value={bill.bill_date} />}
/>
<DetailItem
label={intl.get('vendor_name')}
children={bill.vendor?.display_name}
/>
<DetailItem
label={intl.get('due_date')}
children={<FormatDate value={bill.due_date} />}
/>
</DetailsMenu>
<DetailsMenu direction={'horizantal'} minLabelSize={'140px'}>
<DetailItem
label={intl.get('due_amount')}
children={bill.formatted_due_amount}
/>
<DetailItem
label={intl.get('reference')}
children={defaultTo(bill.reference_no, '--')}
/>
<DetailItem
label={intl.get('bill.details.created_at')}
children={<FormatDate value={bill.created_at} />}
/>
</DetailsMenu>
</div>
);
}

View File

@@ -0,0 +1,29 @@
import React from 'react';
import clsx from 'classnames';
import { Card } from 'components';
import BillDetailActionsBar from './BillDetailActionsBar';
import BillDetailHeader from './BillDetailHeader';
import BillDetailTable from './BillDetailTable';
import { BillDetailFooter } from './BillDetailFooter'
import BillDrawerCls from 'style/components/Drawers/BillDrawer.module.scss';
/**
* Bill detail panel tab.
*/
export default function BillDetailTab() {
return (
<div className={clsx(BillDrawerCls.detail_panel)}>
<BillDetailActionsBar />
<Card>
<BillDetailHeader />
<BillDetailTable />
<BillDetailFooter />
</Card>
</div>
);
}

View File

@@ -0,0 +1,26 @@
import React from 'react';
import clsx from 'classnames';
import { DataTable } from 'components';
import { useBillDrawerContext } from './BillDrawerProvider';
import { useBillReadonlyEntriesTableColumns } from './utils';
import BillDrawerCls from 'style/components/Drawers/BillDrawer.module.scss';
export default function BillDetailTable() {
const { bill: { entries } } = useBillDrawerContext();
// Retrieve bill readonly entries table columns.
const columns = useBillReadonlyEntriesTableColumns();
return (
<div className={clsx(BillDrawerCls.detail_panel_table)}>
<DataTable
columns={columns}
data={entries}
className={'table-constrant'}
/>
</div>
);
}

View File

@@ -0,0 +1,13 @@
import React from 'react';
import BillLocatedLandedCostDeleteAlert from 'containers/Alerts/Bills/BillLocatedLandedCostDeleteAlert';
/**
* Bill drawer alert.
*/
export default function BillDrawerAlerts() {
return (
<div class="bills-alerts">
<BillLocatedLandedCostDeleteAlert name="bill-located-cost-delete" />
</div>
);
}

View File

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

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { Tab } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import { DrawerMainTabs } from 'components';
import BillDetailTab from './BillDetailTab';
import LocatedLandedCostTable from './LocatedLandedCostTable';
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
import { useBillDrawerContext } from './BillDrawerProvider';
import BillDrawerCls from 'style/components/Drawers/BillDrawer.module.scss';
/**
* Bill view details.
*/
export default function BillDrawerDetails() {
const {
data: { transactions },
} = useBillDrawerContext();
return (
<div className={clsx(BillDrawerCls.root)}>
<DrawerMainTabs defaultSelectedTabId="details">
<Tab
title={intl.get('details')}
id={'details'}
panel={<BillDetailTab />}
/>
<Tab
title={intl.get('journal_entries')}
id={'journal_entries'}
panel={<JournalEntriesTable transactions={transactions} />}
/>
<Tab
title={intl.get('located_landed_cost')}
id={'landed_cost'}
panel={<LocatedLandedCostTable />}
/>
</DrawerMainTabs>
</div>
);
}

View File

@@ -0,0 +1,59 @@
import React from 'react';
import intl from 'react-intl-universal';
import { DrawerHeaderContent, DrawerLoading } from 'components';
import {
useBill,
useTransactionsByReference,
useBillLocatedLandedCost,
} from 'hooks/query';
const BillDrawerContext = React.createContext();
/**
* Bill drawer provider.
*/
function BillDrawerProvider({ billId, ...props }) {
// Handle fetch bill details.
const { isLoading: isBillLoading, data: bill } = useBill(billId, {
enabled: !!billId,
});
// Handle fetch transaction by reference.
const { data, isLoading: isTransactionLoading } = useTransactionsByReference(
{
reference_id: billId,
reference_type: 'Bill',
},
{ enabled: !!billId },
);
// Handle fetch bill located landed cost transaction.
const { isLoading: isLandedCostLoading, data: transactions } =
useBillLocatedLandedCost(billId, {
enabled: !!billId,
});
//provider.
const provider = {
transactions,
billId,
data,
bill,
};
const loading = isLandedCostLoading || isTransactionLoading || isBillLoading;
return (
<DrawerLoading loading={loading}>
<DrawerHeaderContent
name="bill-drawer"
title={intl.get('bill_details')}
/>
<BillDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useBillDrawerContext = () => React.useContext(BillDrawerContext);
export { BillDrawerProvider, useBillDrawerContext };

View File

@@ -0,0 +1,91 @@
import React from 'react';
import { DataTable, Card } from 'components';
import { Button, Classes, NavbarGroup } from '@blueprintjs/core';
import { useLocatedLandedCostColumns, ActionsMenu } from './components';
import { useBillDrawerContext } from './BillDrawerProvider';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import Icon from 'components/Icon';
/**
* Located landed cost table.
*/
function LocatedLandedCostTable({
// #withAlertsActions
openAlert,
// #withDialogActions
openDialog,
// #withDrawerActions
openDrawer,
}) {
const columns = useLocatedLandedCostColumns();
const { transactions, billId } = useBillDrawerContext();
// Handle the transaction delete action.
const handleDeleteTransaction = ({ id }) => {
openAlert('bill-located-cost-delete', { BillId: id });
};
// Handle allocate landed cost button click.
const handleAllocateCostClick = () => {
openDialog('allocate-landed-cost', { billId });
};
// Handle from transaction link click.
const handleFromTransactionClick = (original) => {
const { from_transaction_type, from_transaction_id } = original;
switch (from_transaction_type) {
case 'Expense':
openDrawer('expense-drawer', { expenseId: from_transaction_id });
break;
case 'Bill':
default:
openDrawer('bill-drawer', { billId: from_transaction_id });
break;
}
};
return (
<div>
<DashboardActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="receipt-24" />}
text={'Allocate landed cost'}
onClick={handleAllocateCostClick}
/>
</NavbarGroup>
</DashboardActionsBar>
<Card>
<DataTable
columns={columns}
data={transactions}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeleteTransaction,
onFromTranscationClick: handleFromTransactionClick,
}}
className={'datatable--landed-cost-transactions'}
/>
</Card>
</div>
);
}
export default compose(
withAlertsActions,
withDialogActions,
withDrawerActions,
)(LocatedLandedCostTable);

View File

@@ -0,0 +1,75 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
import { safeCallback } from 'utils';
import { Icon } from 'components';
/**
* Actions menu.
*/
export function ActionsMenu({ row: { original }, payload: { onDelete } }) {
return (
<Menu>
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
/>
</Menu>
);
}
/**
* From transaction table cell.
*/
export function FromTransactionCell({
row: { original },
payload: { onFromTranscationClick }
}) {
// Handle the link click
const handleAnchorClick = () => {
onFromTranscationClick && onFromTranscationClick(original);
};
return (
<a href="#" onClick={handleAnchorClick}>
{original.from_transaction_type} {original.from_transaction_id}
</a>
);
}
/**
* Retrieve bill located landed cost table columns.
*/
export function useLocatedLandedCostColumns() {
return React.useMemo(
() => [
{
Header: intl.get('name'),
accessor: 'description',
width: 150,
className: 'name',
},
{
Header: intl.get('amount'),
accessor: 'formatted_amount',
width: 100,
className: 'amount',
},
{
id: 'from_transaction',
Header: intl.get('From transaction'),
Cell: FromTransactionCell,
width: 100,
className: 'from-transaction',
},
{
Header: intl.get('allocation_method'),
accessor: 'allocation_method_formatted',
width: 100,
className: 'allocation-method',
},
],
[],
);
}

View File

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

View File

@@ -0,0 +1,51 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormatNumberCell } from '../../../components';
/**
* Retrieve bill readonly details entries table columns.
*/
export const useBillReadonlyEntriesTableColumns = () =>
React.useMemo(
() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
width: 150,
className: 'item',
disableSortBy: true
},
{
Header: intl.get('description'),
accessor: 'description',
className: 'description',
disableSortBy: true
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true
},
],
[],
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,130 @@
import React from 'react';
import intl from 'react-intl-universal';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
Position,
PopoverInteractionKind,
Popover,
Menu,
MenuItem,
} from '@blueprintjs/core';
import clsx from 'classnames';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { useCustomerDetailsDrawerContext } from './CustomerDetailsDrawerProvider';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { Icon, FormattedMessage as T } from 'components';
import { compose } from 'utils';
/**
* Customer details actions bar.
*/
function CustomerDetailsActionsBar({
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const { contact, customerId } = useCustomerDetailsDrawerContext();
const history = useHistory();
// Handle new invoice button click.
const handleNewInvoiceClick = () => {
history.push('invoices/new');
closeDrawer('customer-details-drawer');
};
// Handle new receipt button click.
const handleNewReceiptClick = () => {
history.push('receipts/new');
closeDrawer('customer-details-drawer');
};
// Handle new payment receive button click.
const handleNewPaymentClick = () => {
history.push('payment-receives/new');
closeDrawer('customer-details-drawer');
};
// Handle new estimate button click.
const handleNewEstimateClick = () => {
history.push('estimates/new');
closeDrawer('customer-details-drawer');
};
const handleDeleteCustomer = () => {
openAlert(`customer-delete`, { contactId: customerId });
closeDrawer('customer-details-drawer');
};
const handleEditContact = () => {
history.push(`/customers/${customerId}/edit`);
closeDrawer('customer-details-drawer');
};
return (
<DashboardActionsBar>
<NavbarGroup>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'customer.drawer.action.new_invoice'} />}
onClick={handleNewInvoiceClick}
/>
<MenuItem
text={<T id={'customer.drawer.action.new_estimate'} />}
onClick={handleNewEstimateClick}
/>
<MenuItem
text={<T id={'customer.drawer.action.new_receipt'} />}
onClick={handleNewReceiptClick}
/>
<MenuItem
text={<T id={'customer.drawer.action.new_payment'} />}
onClick={handleNewPaymentClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={clsx(Classes.MINIMAL)}
text={<T id={'customer.drawer.action.new_transaction'} />}
icon={<Icon icon={'plus'} />}
/>
</Popover>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={intl.get('edit_contact', { name: contact?.contact_service })}
onClick={handleEditContact}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeleteCustomer}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withDrawerActions,
withAlertsActions,
)(CustomerDetailsActionsBar);

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,21 @@
import React from 'react';
import { DrawerBody } from 'components';
import CustomerDetails from './CustomerDetails';
import { CustomerDetailsDrawerProvider } from './CustomerDetailsDrawerProvider';
/**
* Contact detail drawer content.
*/
export default function CustomerDetailsDrawerContent({
// #ownProp
customerId,
}) {
return (
<CustomerDetailsDrawerProvider customerId={customerId}>
<DrawerBody>
<CustomerDetails />
</DrawerBody>
</CustomerDetailsDrawerProvider>
);
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,26 @@
import React from 'react';
import { Tab } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import { DrawerMainTabs } from 'components';
import EstimateDetailPanel from './EstimateDetailPanel';
import clsx from 'classnames';
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
/**
* Estimate view detail
*/
export default function EstimateDetail() {
return (
<div className={clsx(EstimateDetailsCls.root)}>
<DrawerMainTabs>
<Tab
title={intl.get('details')}
id={'details'}
panel={<EstimateDetailPanel />}
/>
</DrawerMainTabs>
</div>
);
}

View File

@@ -0,0 +1,87 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { Icon, FormattedMessage as T } from 'components';
import { compose } from 'utils';
/**
* Estimate read-only details actions bar of the drawer.
*/
function EstimateDetailActionsBar({
// #withDialogActions
openDialog,
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const { estimateId } = useEstimateDetailDrawerContext();
const history = useHistory();
// Handle edit sale estimate.
const handleEditEstimate = () => {
history.push(`/estimates/${estimateId}/edit`);
closeDrawer('estimate-detail-drawer');
};
// Handle delete sale estimate.
const handleDeleteEstimate = () => {
openAlert('estimate-delete', { estimateId });
};
// Handle print estimate.
const handlePrintEstimate = () => {
openDialog('estimate-pdf-preview', { estimateId });
};
return (
<DashboardActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'edit_estimate'} />}
onClick={handleEditEstimate}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" />}
text={<T id={'print'} />}
onClick={handlePrintEstimate}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeleteEstimate}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withDialogActions,
withAlertsActions,
withDrawerActions,
)(EstimateDetailActionsBar);

View File

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

View File

@@ -0,0 +1,37 @@
import React from 'react';
import intl from 'react-intl-universal';
import { useEstimate } from 'hooks/query';
import { DrawerHeaderContent, DrawerLoading } from 'components';
const EstimateDetailDrawerContext = React.createContext();
/**
* Estimate detail provider.
*/
function EstimateDetailDrawerProvider({ estimateId, ...props }) {
// Fetches the estimate by the given id.
const { data: estimate, isLoading: isEstimateLoading } = useEstimate(
estimateId,
{ enabled: !!estimateId },
);
const provider = {
estimateId,
estimate,
};
return (
<DrawerLoading loading={isEstimateLoading}>
<DrawerHeaderContent
name="estimate-detail-drawer"
title={intl.get('estimate_details')}
/>
<EstimateDetailDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useEstimateDetailDrawerContext = () =>
React.useContext(EstimateDetailDrawerContext);
export { EstimateDetailDrawerProvider, useEstimateDetailDrawerContext };

View File

@@ -0,0 +1,40 @@
import React from 'react';
import clsx from 'classnames';
import { T, TotalLines, TotalLine, If } from 'components';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
import { FormatNumber } from '../../../components';
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
/**
* Estimate details panel footer content.
*/
export default function EstimateDetailFooter() {
const { estimate } = useEstimateDetailDrawerContext();
return (
<div className={clsx(EstimateDetailsCls.detail_panel_footer)}>
<TotalLines className={clsx(EstimateDetailsCls.total_lines)}>
<TotalLine
title={<T id={'estimate.details.subtotal'} />}
value={<FormatNumber value={estimate.amount} />}
className={EstimateDetailsCls.total_line_subtotal}
/>
<TotalLine
title={<T id={'estimate.details.total'} />}
value={estimate.formatted_amount}
className={EstimateDetailsCls.total_line_total}
/>
</TotalLines>
<If condition={false}>
<div className={clsx(EstimateDetailsCls.detail_panel_note)}>
<p>
<b><T id={'estimate.details.note'} />:</b> --
</p>
</div>
</If>
</div>
);
}

View File

@@ -0,0 +1,53 @@
import React from 'react';
import intl from 'react-intl-universal';
import { defaultTo } from 'lodash';
import clsx from 'classnames';
import { FormatDate, T, DetailsMenu, DetailItem } from 'components';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
/**
* Estimate read-only details drawer header.
*/
export default function EstimateDetailHeader() {
const { estimate } = useEstimateDetailDrawerContext();
return (
<div className={clsx(EstimateDetailsCls.detail_panel_header)}>
<DetailsMenu>
<DetailItem label={intl.get('amount')}>
<span class="big-number">{estimate.formatted_amount}</span>
</DetailItem>
<DetailItem
label={intl.get('estimate.details.estimate_number')}
children={defaultTo(estimate.estimate_number, '-')}
/>
<DetailItem
label={intl.get('customer_name')}
children={estimate.customer?.display_name}
/>
<DetailItem
label={intl.get('estimate_date')}
children={estimate.formatted_estimate_date}
/>
<DetailItem
label={intl.get('expiration_date')}
children={estimate.formatted_expiration_date}
/>
</DetailsMenu>
<DetailsMenu direction={'horizantal'} minLabelSize={'140px'}>
<DetailItem
label={intl.get('reference')}
children={defaultTo(estimate.reference, '-')}
/>
<DetailItem
label={<T id={'estimate.details.created_at'} />}
children={<FormatDate value={estimate.created_at} />}
/>
</DetailsMenu>
</div>
);
}

View File

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

View File

@@ -0,0 +1,31 @@
import React from 'react';
import clsx from 'classnames';
import { DataTable } from 'components';
import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider';
import { useEstimateReadonlyEntriesColumns } from './utils';
import EstimateDetailsCls from 'style/components/Drawers/EstimateDetails.module.scss';
/**
* Estimate detail table.
*/
export default function EstimateDetailTable() {
const {
estimate: { entries },
} = useEstimateDetailDrawerContext();
// Estimate entries table columns.
const columns = useEstimateReadonlyEntriesColumns();
return (
<div className={clsx(EstimateDetailsCls.detail_panel_table)}>
<DataTable
columns={columns}
data={entries}
className={'table-constrant'}
/>
</div>
);
}

View File

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

View File

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

View File

@@ -0,0 +1,47 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormatNumberCell } from '../../../components';
/**
* Retrieve table columns of estimate readonly entries details.
*/
export const useEstimateReadonlyEntriesColumns = () =>
React.useMemo(() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
width: 150,
className: 'name',
disableSortBy: true,
},
{
Header: intl.get('description'),
accessor: 'description',
className: 'description',
disableSortBy: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true,
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true,
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true,
},
], []);

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
import React from 'react';
import { Card } from 'components';
import ExpenseDrawerActionBar from './ExpenseDrawerActionBar';
import ExpenseDrawerHeader from './ExpenseDrawerHeader';
import ExpenseDrawerTable from './ExpenseDrawerTable';
import ExpenseDrawerFooter from './ExpenseDrawerFooter';
/**
* Expense view details.
*/
export default function ExpenseDrawerDetails() {
return (
<div className={'expense-drawer'}>
<ExpenseDrawerActionBar />
<div className="expense-drawer__content">
<Card>
<ExpenseDrawerHeader />
<ExpenseDrawerTable />
<ExpenseDrawerFooter />
</Card>
</div>
</div>
);
}

View File

@@ -0,0 +1,33 @@
import React from 'react';
import { T } from 'components';
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
import { FormatNumber } from '../../../components';
/**
* Footer details of expense readonly details.
*/
export default function ExpenseDrawerFooter() {
const { expense } = useExpenseDrawerContext();
return (
<div className="expense-drawer__content-footer">
<div class="total-lines">
<div class="total-lines__line total-lines__line--subtotal">
<div class="title">
<T id={'expense.details.subtotal'} />
</div>
<div class="amount">
{<FormatNumber value={expense.total_amount} />}
</div>
</div>
<div class="total-lines__line total-lines__line--total">
<div class="title">
<T id={'expense.details.total'} />
</div>
<div class="amount">{expense.formatted_amount}</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,58 @@
import React from 'react';
import moment from 'moment';
import { defaultTo } from 'lodash';
import { DetailItem, DetailsMenu, Money } from 'components';
import { FormattedMessage as T } from 'components';
import { useExpenseDrawerContext } from './ExpenseDrawerProvider';
/**
* Expense drawer content.
*/
export default function ExpenseDrawerHeader() {
const {
expense: {
total_amount,
payment_date,
currency_code,
reference_no,
description,
published_at,
},
} = useExpenseDrawerContext();
return (
<div className={'expense-drawer__content-header'}>
<DetailsMenu>
<DetailItem name={'amount'} label={<T id={'full_amount'} />}>
<h3 class="big-number">
<Money amount={total_amount} currency={currency_code} />
</h3>
</DetailItem>
<DetailItem name={'date'} label={<T id={'date'} />}>
{moment(payment_date).format('YYYY MMM DD')}
</DetailItem>
<DetailItem name={'currency'} label={<T id={'currency'} />}>
{currency_code}
</DetailItem>
<DetailItem name={'reference'} label={<T id={'reference_no'} />}>
{defaultTo(reference_no, '-')}
</DetailItem>
<DetailItem label={<T id={'published_at'} />}>
{moment(published_at).format('YYYY MMM DD')}
</DetailItem>
</DetailsMenu>
<DetailsMenu direction={'horizantal'}>
<DetailItem label={<T id={'description'} />}>
{defaultTo(description, '—')}
</DetailItem>
<DetailItem label={<T id={'created_at'} />}>2021 Aug 24</DetailItem>
</DetailsMenu>
</div>
);
}

View File

@@ -0,0 +1,38 @@
import React from 'react';
import { useExpense } from 'hooks/query';
import { DrawerLoading } from 'components';
const ExpenseDrawerDrawerContext = React.createContext();
/**
* Expense drawer provider.
*/
function ExpenseDrawerProvider({ expenseId, ...props }) {
// Fetch the expense details.
const {
data: expense,
isLoading: isExpenseLoading,
isFetching: isExpenseFetching,
} = useExpense(expenseId, {
enabled: !!expenseId,
});
// Provider.
const provider = {
expenseId,
expense,
isExpenseFetching,
isExpenseLoading,
};
return (
<DrawerLoading loading={isExpenseLoading}>
<ExpenseDrawerDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useExpenseDrawerContext = () =>
React.useContext(ExpenseDrawerDrawerContext);
export { ExpenseDrawerProvider, useExpenseDrawerContext };

View File

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

View File

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

View File

@@ -0,0 +1,37 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormatNumberCell } from '../../../components';
/**
* Retrieve expense readonly details entries table columns.
*/
export const useExpenseReadEntriesColumns = () =>
React.useMemo(
() => [
{
Header: intl.get('expense_account'),
accessor: 'expense_account.name',
width: 110,
disableSortBy: true,
className: 'account',
},
{
Header: intl.get('description'),
accessor: 'description',
width: 110,
disableSortBy: true,
className: 'description',
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: 100,
disableSortBy: true,
className: 'amount',
align: 'right',
},
],
[],
);

View File

@@ -0,0 +1,26 @@
import React from 'react';
import clsx from 'classnames';
import { Card } from 'components';
import InventoryAdjustmentDetailActionsBar from './InventoryAdjustmentDetailActionsBar';
import InventoryAdjustmentDetailHeader from './InventoryAdjustmentDetailHeader';
import InventoryAdjustmentDetailTable from './InventoryAdjustmentDetailTable';
import InventoryAdjustmentDrawerCls from 'style/components/Drawers/InventoryAdjustmentDrawer.module.scss';
/**
* Inventory adjustment detail
*/
export default function InventoryAdjustmentDetail() {
return (
<div className={clsx(InventoryAdjustmentDrawerCls.detail_panel)}>
<InventoryAdjustmentDetailActionsBar />
<Card>
<InventoryAdjustmentDetailHeader />
<InventoryAdjustmentDetailTable />
</Card>
</div>
);
}

View File

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

View File

@@ -0,0 +1,58 @@
import React from 'react';
import moment from 'moment';
import intl from 'react-intl-universal';
import { defaultTo } from 'lodash';
import clsx from 'classnames';
import { DetailsMenu, DetailItem } from 'components';
import { useInventoryAdjustmentDrawerContext } from './InventoryAdjustmentDrawerProvider';
import InventoryAdjustmentDrawerCls from 'style/components/Drawers/InventoryAdjustmentDrawer.module.scss';
/**
* Inventory detail header.
*/
export default function InventoryAdjustmentDetailHeader() {
const {
inventoryAdjustment: {
date,
type,
adjustment_account,
inventory_direction,
description,
reference_no,
reason,
published_at,
created_at,
},
} = useInventoryAdjustmentDrawerContext();
return (
<div className={clsx(InventoryAdjustmentDrawerCls.detail_panel_header)}>
<DetailsMenu>
<DetailItem label={intl.get('date')}>
{moment(date).format('YYYY MMM DD')}
</DetailItem>
<DetailItem label={intl.get('type')}>{type}</DetailItem>
<DetailItem label={intl.get('adjustment_account')}>
{adjustment_account.name}
</DetailItem>
<DetailItem name={'reference'} label={intl.get('reference_no')}>
{defaultTo(reference_no, '-')}
</DetailItem>
<DetailItem label={intl.get('published_at')}>
{moment(published_at).format('YYYY MMM DD')}
</DetailItem>
</DetailsMenu>
<DetailsMenu direction={'horizantal'}>
<DetailItem label={intl.get('reason')}>
{defaultTo(reason, '—')}
</DetailItem>
<DetailItem label={intl.get('created_at')}>
{moment(created_at).format('YYYY MMM DD')}
</DetailItem>
</DetailsMenu>
</div>
);
}

View File

@@ -0,0 +1,30 @@
import React from 'react';
import clsx from 'classnames';
import { DataTable } from 'components';
import { useInventoryAdjustmentEntriesColumns } from './utils';
import { useInventoryAdjustmentDrawerContext } from './InventoryAdjustmentDrawerProvider';
import InventoryAdjustmentDrawerCls from 'style/components/Drawers/InventoryAdjustmentDrawer.module.scss';
/**
* Inventory adjustment detail entries table.
*/
export default function InventoryAdjustmentDetailTable() {
const columns = useInventoryAdjustmentEntriesColumns();
const {
inventoryAdjustment: { entries },
} = useInventoryAdjustmentDrawerContext();
return (
<div className={clsx(InventoryAdjustmentDrawerCls.detail_panel_table)}>
<DataTable
columns={columns}
data={entries}
className={'table-constrant'}
/>
</div>
);
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,37 @@
import React from 'react';
import intl from 'react-intl-universal';
export const useInventoryAdjustmentEntriesColumns = () =>
React.useMemo(
() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
width: 150,
className: 'name',
disableSortBy: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
width: 100,
align: 'right',
disableSortBy: true,
},
{
Header: intl.get('cost'),
accessor: 'cost',
width: 100,
align: 'right',
disableSortBy: true,
},
{
Header: intl.get('value'),
accessor: 'value',
width: 100,
align: 'right',
disableSortBy: true,
},
],
[],
);

View File

@@ -0,0 +1,36 @@
import React from 'react';
import { Tab } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import { DrawerMainTabs } from 'components';
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
import InvoiceDetailTab from './InvoiceDetailTab';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss';
/**
* Invoice view detail.
*/
export default function InvoiceDetail() {
const { transactions } = useInvoiceDetailDrawerContext();
return (
<div className={clsx(InvoiceDrawerCls.invoice_details)}>
<DrawerMainTabs defaultSelectedTabId="details">
<Tab
title={intl.get('details')}
id={'details'}
panel={<InvoiceDetailTab />}
/>
<Tab
title={intl.get('journal_entries')}
id={'journal_entries'}
panel={<JournalEntriesTable transactions={transactions} />}
/>
</DrawerMainTabs>
</div>
);
}

View File

@@ -0,0 +1,89 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { Icon, FormattedMessage as T } from 'components';
import { compose } from 'utils';
/**
* Invoice details action bar.
*/
function InvoiceDetailActionsBar({
// #withDialogActions
openDialog,
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
// Invoice detail drawer context.
const { invoiceId } = useInvoiceDetailDrawerContext();
// Handle edit sale invoice.
const handleEditInvoice = () => {
history.push(`/invoices/${invoiceId}/edit`);
closeDrawer('invoice-detail-drawer');
};
// Handle delete sale invoice.
const handleDeleteInvoice = () => {
openAlert('invoice-delete', { invoiceId });
};
// Handle print invoices.
const handlePrintInvoice = () => {
openDialog('invoice-pdf-preview', { invoiceId });
};
return (
<DashboardActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'edit_invoice'} />}
onClick={handleEditInvoice}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon="print-16" />}
text={<T id={'print'} />}
onClick={handlePrintInvoice}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeleteInvoice}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withDialogActions,
withDrawerActions,
withAlertsActions,
)(InvoiceDetailActionsBar);

View File

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

View File

@@ -0,0 +1,48 @@
import React from 'react';
import intl from 'react-intl-universal';
import { DrawerHeaderContent, DrawerLoading } from 'components';
import { useTransactionsByReference, useInvoice } from 'hooks/query';
const InvoiceDetailDrawerContext = React.createContext();
/**
* Invoice detail provider.
*/
function InvoiceDetailDrawerProvider({ invoiceId, ...props }) {
// Handle fetch transaction by reference.
const {
data: { transactions },
isLoading: isTransactionLoading,
} = useTransactionsByReference(
{
reference_id: invoiceId,
reference_type: 'SaleInvoice',
},
{ enabled: !!invoiceId },
);
// Fetch sale invoice details.
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId, {
enabled: !!invoiceId,
});
//provider.
const provider = {
transactions,
invoiceId,
invoice,
};
return (
<DrawerLoading loading={isTransactionLoading || isInvoiceLoading}>
<DrawerHeaderContent
name="invoice-detail-drawer"
title={intl.get('invoice_details')}
/>
<InvoiceDetailDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useInvoiceDetailDrawerContext = () =>
React.useContext(InvoiceDetailDrawerContext);
export { InvoiceDetailDrawerProvider, useInvoiceDetailDrawerContext };

View File

@@ -0,0 +1,40 @@
import React from 'react';
import clsx from 'classnames';
import { T, TotalLines, FormatNumber, TotalLine } from 'components';
import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
/**
* Invoice details footer.
*/
export function InvoiceDetailFooter() {
const { invoice } = useInvoiceDetailDrawerContext();
return (
<div className={clsx(InvoiceDrawerCls.detail_panel_footer)}>
<TotalLines>
<TotalLine
title={<T id={'invoice.details.subtotal'} />}
value={<FormatNumber value={invoice.balance} />}
className={InvoiceDrawerCls.total_line_subtotal}
/>
<TotalLine
title={<T id={'invoice.details.total'} />}
value={invoice.formatted_amount}
className={InvoiceDrawerCls.total_line_total}
/>
<TotalLine
title={<T id={'invoice.details.payment_amount'} />}
value={invoice.formatted_payment_amount}
className={InvoiceDrawerCls.total_line_payment}
/>
<TotalLine
title={<T id={'invoice.details.due_amount'} />}
value={invoice.formatted_due_amount}
className={InvoiceDrawerCls.total_line_dueAmount}
/>
</TotalLines>
</div>
);
}

View File

@@ -0,0 +1,57 @@
import React from 'react';
import intl from 'react-intl-universal';
import { defaultTo } from 'lodash';
import clsx from 'classnames';
import { DetailsMenu, DetailItem, FormatDate } from 'components';
import { useInvoiceDetailDrawerContext } from './InvoiceDetailDrawerProvider';
import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss';
/**
* Invoice detail header.
*/
export default function InvoiceDetailHeader() {
const { invoice } = useInvoiceDetailDrawerContext();
return (
<div className={clsx(InvoiceDrawerCls.detail_panel_header)}>
<DetailsMenu>
<DetailItem label={intl.get('amount')}>
<h3 class="big-number">{invoice.formatted_amount}</h3>
</DetailItem>
<DetailItem
label={intl.get('invoice_no')}
children={invoice.invoice_no}
/>
<DetailItem
label={intl.get('customer_name')}
children={invoice.customer?.display_name}
/>
<DetailItem
label={intl.get('invoice_date')}
children={<FormatDate value={invoice.invoice_date} />}
/>
<DetailItem
label={intl.get('due_date')}
children={<FormatDate value={invoice.due_date} />}
/>
</DetailsMenu>
<DetailsMenu direction={'horizantal'} minLabelSize={'140px'}>
<DetailItem
label={intl.get('due_amount')}
children={invoice.formatted_due_amount}
/>
<DetailItem
label={intl.get('reference')}
children={defaultTo(invoice.reference_no, '--')}
/>
<DetailItem
label={intl.get('invoice.details.created_at')}
children={<FormatDate value={invoice.created_at} />}
/>
</DetailsMenu>
</div>
);
}

View File

@@ -0,0 +1,28 @@
import React from 'react';
import clsx from 'classnames';
import { Card } from 'components';
import InvoiceDetailActionsBar from './InvoiceDetailActionsBar';
import InvoiceDetailHeader from './InvoiceDetailHeader';
import InvoiceDetailTable from './InvoiceDetailTable';
import { InvoiceDetailFooter } from './InvoiceDetailFooter';
import InvoiceDrawerCls from 'style/components/Drawers/InvoiceDrawer.module.scss';
/**
* Invoice readonly details tab panel.
*/
export default function InvoiceDetailTab() {
return (
<div className={clsx(InvoiceDrawerCls.detail_panel)}>
<InvoiceDetailActionsBar />
<Card>
<InvoiceDetailHeader />
<InvoiceDetailTable />
<InvoiceDetailFooter />
</Card>
</div>
);
}

View File

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

View File

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

View File

@@ -0,0 +1,50 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormatNumberCell } from '../../../components';
/**
* Retrieve invoice readonly details table columns.
*/
export const useInvoiceReadonlyEntriesColumns = () =>
React.useMemo(
() => [
{
Header: intl.get('product_and_service'),
accessor: 'item.name',
width: 150,
className: 'name',
disableSortBy: true,
},
{
Header: intl.get('description'),
accessor: 'description',
className: 'description',
disableSortBy: true,
},
{
Header: intl.get('quantity'),
accessor: 'quantity',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true,
},
{
Header: intl.get('rate'),
accessor: 'rate',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true,
},
{
Header: intl.get('amount'),
accessor: 'amount',
Cell: FormatNumberCell,
width: 100,
align: 'right',
disableSortBy: true,
},
],
[],
);

View File

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

View File

@@ -0,0 +1,72 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { useItemDetailDrawerContext } from './ItemDetailDrawerProvider';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { Icon, FormattedMessage as T } from 'components';
import { compose } from 'utils';
/**
* Item action-bar of readonly details drawer.
*/
function ItemDetailActionsBar({
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
// Item readonly drawer context.
const { itemId } = useItemDetailDrawerContext();
const history = useHistory();
// Handle edit item.
const handleEditItem = () => {
history.push(`/items/${itemId}/edit`);
closeDrawer('item-detail-drawer');
};
// Handle delete item.
const handleDeleteItem = () => {
openAlert('item-delete', { itemId });
};
return (
<DashboardActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'edit_item'} />}
onClick={handleEditItem}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeleteItem}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withDrawerActions,
withAlertsActions,
)(ItemDetailActionsBar);

View File

@@ -0,0 +1,23 @@
import React from 'react';
import 'style/components/Drawers/ItemDrawer.scss';
import { DrawerBody } from 'components';
import ItemContentDetails from './ItemContentDetails';
import { ItemDetailDrawerProvider } from './ItemDetailDrawerProvider';
/**
* Item detail drawer content.
*/
export default function ItemDetailDrawerContent({
// #ownProp
itemId,
}) {
return (
<ItemDetailDrawerProvider itemId={itemId}>
<DrawerBody>
<ItemContentDetails />
</DrawerBody>
</ItemDetailDrawerProvider>
);
}

View File

@@ -0,0 +1,33 @@
import React from 'react';
import { DrawerHeaderContent, DrawerLoading } from 'components';
import { useItem } from 'hooks/query';
const ItemDetailDrawerContext = React.createContext();
/**
* Item detail provider
*/
function ItemDetailDrawerProvider({ itemId, ...props }) {
// Fetches the given item detail.
const { isLoading: isItemLoading, data: item } = useItem(itemId, {
enabled: !!itemId,
});
//provider.
const provider = {
item,
itemId,
isItemLoading,
};
return (
<DrawerLoading loading={isItemLoading}>
<DrawerHeaderContent name="item-detail-drawer" title={item?.name} />
<ItemDetailDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useItemDetailDrawerContext = () =>
React.useContext(ItemDetailDrawerContext);
export { ItemDetailDrawerProvider, useItemDetailDrawerContext };

View File

@@ -0,0 +1,82 @@
import React from 'react';
import intl from 'react-intl-universal';
import { defaultTo } from 'lodash';
import classNames from 'classnames';
import { If, DetailsMenu, DetailItem } from 'components';
import { useItemDetailDrawerContext } from './ItemDetailDrawerProvider';
/**
* Item header drawer of readonly details.
*/
export default function ItemDetailHeader() {
const { item } = useItemDetailDrawerContext();
return (
<div class="item-drawer__content">
<DetailsMenu direction={'vertical'}>
<DetailItem
name={'name'}
label={intl.get('item_name')}
children={item.name}
/>
<DetailItem
label={intl.get('sell_price')}
children={item.sell_price_formatted}
align={'right'}
/>
<DetailItem
label={intl.get('cost_price')}
children={item.cost_price_formatted}
align={'right'}
/>
</DetailsMenu>
<DetailsMenu direction={'horizantal'}>
<DetailItem label={intl.get('item_type')} children={item.type} />
<DetailItem
label={intl.get('item_code')}
children={defaultTo(item.code, '-')}
/>
<If condition={item.type === 'inventory'}>
<DetailItem name={'quantity'} label={intl.get('quantity_on_hand')}>
<span
className={classNames({
mines: item.quantity_on_hand <= 0,
plus: item.quantity_on_hand > 0,
})}
>
{defaultTo(item.quantity_on_hand, '-')}
</span>
</DetailItem>
</If>
<DetailItem
label={intl.get('category_name')}
children={defaultTo(item.category?.name, '-')}
/>
<DetailItem
label={intl.get('cost_account_id')}
children={defaultTo(item.cost_account?.name, '-')}
/>
<If condition={item.type === 'inventory'}>
<DetailItem
label={intl.get('inventory_account')}
children={defaultTo(item?.inventory_account?.name, '-')}
/>
</If>
<DetailItem
label={intl.get('sell_account_id')}
children={defaultTo(item?.sell_account?.name, '-')}
/>
<DetailItem
label={intl.get('item.sell_description')}
children={defaultTo(item.sell_description, '-')}
/>
<DetailItem
label={intl.get('item.purchase_description')}
children={defaultTo(item.cost_description, '-')}
/>
</DetailsMenu>
</div>
);
}

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { Drawer, DrawerSuspense } from 'components';
import withDrawers from 'containers/Drawer/withDrawers';
import { compose } from 'utils';
const ItemDetailDrawerContent = React.lazy(() =>
import('./ItemDetailDrawerContent'),
);
/**
* Item Detail drawer.
*/
function ItemDetailDrawer({
name,
// #withDrawer
isOpen,
payload: { itemId },
}) {
return (
<Drawer isOpen={isOpen} name={name} size={'750px'}>
<DrawerSuspense>
<ItemDetailDrawerContent itemId={itemId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers())(ItemDetailDrawer);

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import Icon from 'components/Icon';
import {
Button,
Classes,
NavbarGroup,
Intent,
NavbarDivider,
} from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
import { useManualJournalDrawerContext } from './ManualJournalDrawerProvider';
/**
* Manual journal action bar.
*/
function ManualJournalDrawerActionBar({
// #withAlertsDialog
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
const { manualJournalId } = useManualJournalDrawerContext();
// Handle edit manual journal action.
const handleEditManualJournal = () => {
history.push(`/manual-journals/${manualJournalId}/edit`);
closeDrawer('journal-drawer');
};
// Handle manual journal delete action.
const handleDeleteManualJournal = () => {
openAlert('journal-delete', { manualJournalId });
};
return (
<DashboardActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'edit_journal'} />}
onClick={handleEditManualJournal}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon="trash-16" iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeleteManualJournal}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withAlertsActions,
withDrawerActions,
)(ManualJournalDrawerActionBar);

View File

@@ -0,0 +1,23 @@
import React from 'react';
import 'style/components/Drawers/ManualJournalDrawer.scss';
import { DrawerBody } from 'components';
import { ManualJournalDrawerProvider } from './ManualJournalDrawerProvider';
import ManualJournalDrawerDetails from './ManualJournalDrawerDetails';
/**
* Manual Journal drawer content.
*/
export default function ManualJournalDrawerContent({
// #ownProp
manualJournalId,
}) {
return (
<ManualJournalDrawerProvider manualJournalId={manualJournalId}>
<DrawerBody>
<ManualJournalDrawerDetails />
</DrawerBody>
</ManualJournalDrawerProvider>
);
}

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { Card } from 'components';
import ManualJournalDrawerActionBar from './ManualJournalDrawerActionBar';
import ManualJournalDrawerHeader from './ManualJournalDrawerHeader';
import ManualJournalDrawerTable from './ManualJournalDrawerTable';
import ManualJournalDrawerFooter from './ManualJournalDrawerFooter';
import { useManualJournalDrawerContext } from 'containers/Drawers/ManualJournalDrawer/ManualJournalDrawerProvider';
/**
* Manual journal view details.
*/
export default function ManualJournalDrawerDetails() {
const { manualJournal } = useManualJournalDrawerContext();
return (
<div className={'journal-drawer'}>
<ManualJournalDrawerActionBar manualJournal={manualJournal} />
<div className="journal-drawer__content">
<Card>
<ManualJournalDrawerHeader />
<ManualJournalDrawerTable />
<ManualJournalDrawerFooter />
</Card>
</div>
</div>
);
}

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { useManualJournalDrawerContext } from './ManualJournalDrawerProvider';
import { FormatNumber } from '../../../components';
/**
* Manual journal readonly details footer.
*/
export default function ManualJournalDrawerFooter() {
const {
manualJournal: { amount, formatted_amount },
} = useManualJournalDrawerContext();
return (
<div className="journal-drawer__content-footer">
<div class="total-lines">
<div class="total-lines__line total-lines__line--subtotal">
<div class="title">Subtotal</div>
<div class="debit">
<FormatNumber value={amount} />
</div>
<div class="credit">
<FormatNumber value={amount} />
</div>
</div>
<div class="total-lines__line total-lines__line--total">
<div class="title">TOTAL</div>
<div class="debit">{formatted_amount}</div>
<div class="credit">{formatted_amount}</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { defaultTo } from 'lodash';
import { DetailsMenu, DetailItem, FormattedMessage as T } from 'components';
import { useManualJournalDrawerContext } from './ManualJournalDrawerProvider';
/**
* Manual journal details header.
*/
export default function ManualJournalDrawerHeader() {
const {
manualJournal: {
formatted_amount,
journal_type,
journal_number,
reference,
currency_code,
description,
},
} = useManualJournalDrawerContext();
return (
<div className={'journal-drawer__content-header'}>
<DetailsMenu>
<DetailItem name={'total'} label={<T id={'total'} />}>
<h3 class="amount">{formatted_amount}</h3>
</DetailItem>
<DetailItem name={'journal-type'} label={<T id={'journal_type'} />}>
{journal_type}
</DetailItem>
<DetailItem name={'journal-number'} label={<T id={'journal_no'} />}>
{journal_number}
</DetailItem>
<DetailItem name={'reference-no'} label={<T id={'reference_no'} />}>
{defaultTo(reference, '-')}
</DetailItem>
<DetailItem name={'currency'} label={<T id={'currency'} />}>
{currency_code}
</DetailItem>
</DetailsMenu>
<div class="journal-drawer__content-description">
<b class="title">Description</b>: {defaultTo(description, '')}
</div>
</div>
);
}

View File

@@ -0,0 +1,46 @@
import React from 'react';
import intl from 'react-intl-universal';
import { useJournal } from 'hooks/query';
import { DrawerLoading, DrawerHeaderContent } from 'components';
const ManualJournalDrawerContext = React.createContext();
/**
* Manual journal drawer provider.
*/
function ManualJournalDrawerProvider({ manualJournalId, ...props }) {
// Fetch the specific manual journal details.
const {
data: manualJournal,
isLoading: isJournalLoading,
isFetching: isJournalFetching,
} = useJournal(manualJournalId, {
enabled: !!manualJournalId,
});
// Provider.
const provider = {
manualJournalId,
manualJournal,
isJournalFetching,
isJournalLoading,
};
return (
<DrawerLoading loading={isJournalLoading}>
<DrawerHeaderContent
name={'journal-drawer'}
title={intl.get('manual_journal_number', {
number: manualJournal?.journal_number,
})}
/>
<ManualJournalDrawerContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const useManualJournalDrawerContext = () =>
React.useContext(ManualJournalDrawerContext);
export { ManualJournalDrawerProvider, useManualJournalDrawerContext };

View File

@@ -0,0 +1,27 @@
import React from 'react';
import { DataTable, If } from 'components';
import { useManualJournalEntriesColumns } from './utils';
import { useManualJournalDrawerContext } from './ManualJournalDrawerProvider';
/**
* Manual journal drawer table.
*/
export default function ManualJournalDrawerTable() {
const columns = useManualJournalEntriesColumns();
const {
manualJournal: { entries, description },
} = useManualJournalDrawerContext();
return (
<div className="journal-drawer__content-table">
<DataTable columns={columns} data={entries} />
<If condition={description}>
<p className={'desc'}>
<b>Description</b>: {description}
</p>
</If>
</div>
);
}

View File

@@ -0,0 +1,35 @@
import React, { lazy } from 'react';
import { Drawer, DrawerSuspense } from 'components';
import withDrawers from 'containers/Drawer/withDrawers';
import { compose } from 'utils';
const ManualJournalDrawerContent = lazy(() =>
import('./ManualJournalDrawerContent'),
);
/**
* Manual journal drawer.
*/
function ManualJournalDrawer({
name,
// #withDrawer
isOpen,
payload: { manualJournalId },
}) {
return (
<Drawer
isOpen={isOpen}
name={name}
size={'65%'}
style={{ minWidth: '700px', maxWidth: '900px' }}
>
<DrawerSuspense>
<ManualJournalDrawerContent manualJournalId={manualJournalId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers())(ManualJournalDrawer);

View File

@@ -0,0 +1,76 @@
import intl from 'react-intl-universal';
import React from 'react';
import { Classes, Tooltip, Position } from '@blueprintjs/core';
import { FormatNumberCell, If, Icon } from '../../../components';
/**
* Note column accessor.
*/
export function NoteAccessor(row) {
return (
<If condition={row.note}>
<Tooltip
className={Classes.TOOLTIP_INDICATOR}
content={row.note}
position={Position.LEFT_TOP}
hoverOpenDelay={50}
>
<Icon icon={'file-alt'} iconSize={16} />
</Tooltip>
</If>
);
}
/**
* Retrieve read-only manual journal entries columns.
*/
export const useManualJournalEntriesColumns = () =>
React.useMemo(
() => [
{
Header: intl.get('account_name'),
accessor: 'account.name',
width: 130,
disableSortBy: true,
className: 'account',
},
{
Header: intl.get('contact'),
accessor: 'contact.display_name',
width: 130,
disableSortBy: true,
className: 'contact',
},
{
Header: intl.get('note'),
accessor: NoteAccessor,
width: 80,
disableSortBy: true,
className: 'note',
},
{
Header: intl.get('credit'),
accessor: 'credit',
Cell: FormatNumberCell,
width: 100,
disableResizable: true,
disableSortBy: true,
formatNumber: { noZero: true },
className: 'credit',
align: 'right',
},
{
Header: intl.get('debit'),
accessor: 'debit',
Cell: FormatNumberCell,
width: 100,
disableResizable: true,
disableSortBy: true,
formatNumber: { noZero: true },
className: 'debit',
align: 'right',
},
],
[],
);

View File

@@ -0,0 +1,51 @@
import React from 'react';
import 'style/components/Drawers/DrawerTemplate.scss';
import PaperTemplateHeader from './PaperTemplateHeader';
import PaperTemplateTable from './PaperTemplateTable';
import PaperTemplateFooter from './PaperTemplateFooter';
import { updateItemsEntriesTotal } from 'containers/Entries/utils';
import intl from 'react-intl-universal';
function PaperTemplate({ labels: propLabels, paperData, entries }) {
const labels = {
name: intl.get('estimate_'),
billedTo: intl.get('billed_to'),
date: intl.get('estimate_date'),
refNo: intl.get('estimate_no'),
billedFrom: intl.get('billed_from'),
amount: intl.get('estimate_amount'),
dueDate: intl.get('due_date_'),
...propLabels,
};
const defaultValues = {
billedTo: paperData.customer.display_name,
date: paperData.estimate_date,
amount: '',
billedFrom: '',
dueDate: paperData.expiration_date,
referenceNo: paperData.estimate_number,
...paperData,
};
return (
<div id={'page-size'}>
<div className={'template'}>
<PaperTemplateHeader
defaultLabels={labels}
headerData={defaultValues}
/>
<PaperTemplateTable
tableData={updateItemsEntriesTotal(entries)}
currencyCode={paperData.currency_code}
/>
<PaperTemplateFooter footerData={defaultValues} />
</div>
</div>
);
}
export default PaperTemplate;

View File

@@ -0,0 +1,23 @@
import React from 'react';
import { If } from 'components';
import intl from 'react-intl-universal';
export default function PaperTemplateFooter({
footerData: { terms_conditions },
}) {
return (
<div className="template__terms">
<If condition={terms_conditions}>
<div className="template__terms__title">
<h4>{intl.get('conditions_and_terms')}</h4>
</div>
<ul>
{[terms_conditions].map((terms) => (
<li>{terms}</li>
))}
</ul>
</If>
</div>
);
}

View File

@@ -0,0 +1,23 @@
import React from 'react';
import { TemplateHeader, TemplateContent } from '../components';
export default function PaperTemplateHeader({
defaultLabels,
headerData: { referenceNo, amount, dueDate, date, billedTo, currency_code },
}) {
return (
<>
<TemplateHeader defaultLabels={defaultLabels} />
<TemplateContent
defaultLabels={defaultLabels}
billedTo={billedTo}
date={date}
referenceNo={referenceNo}
amount={amount}
billedFrom={''}
dueDate={dueDate}
currencyCode={currency_code}
/>
</>
);
}

View File

@@ -0,0 +1,43 @@
import React, { useMemo } from 'react';
import intl from 'react-intl-universal';
import { DataTable, Money } from 'components';
export default function DrawerTemplateTable({ tableData, currencyCode }) {
const columns = useMemo(
() => [
{
Header: intl.get('description'),
accessor: 'description',
disableSortBy: true,
width: 150,
},
{
Header: intl.get('rate'),
accessor: 'rate',
accessor: ({ rate }) => <Money amount={rate} currency={currencyCode} />,
disableSortBy: true,
width: 80,
},
{
Header: intl.get('qty'),
accessor: 'quantity',
disableSortBy: true,
width: 80,
},
{
Header: intl.get('total'),
accessor: ({ total }) => (
<Money amount={total} currency={currencyCode} />
),
disableSortBy: true,
width: 70,
},
],
[],
);
return (
<div className="template__table">
<DataTable columns={columns} data={tableData} />
</div>
);
}

View File

@@ -0,0 +1,74 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
} from '@blueprintjs/core';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { Icon, FormattedMessage as T } from 'components';
import { compose } from 'utils';
/**
* Payment made - Details panel - actions bar.
*/
function PaymentMadeDetailActionsBar({
// #withAlertsActions
openAlert,
// #withDrawerActions
closeDrawer,
}) {
const history = useHistory();
const { paymentMadeId } = usePaymentMadeDetailContext();
// Handle edit payment made.
const handleEditPaymentMade = () => {
history.push(`/payment-mades/${paymentMadeId}/edit`);
closeDrawer('payment-made-detail-drawer');
};
// Handle delete payment made.
const handleDeletePaymentMade = () => {
openAlert('payment-made-delete', { paymentMadeId });
};
return (
<DashboardActionsBar>
<NavbarGroup>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="pen-18" />}
text={<T id={'edit_payment_made'} />}
onClick={handleEditPaymentMade}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleDeletePaymentMade}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withDialogActions,
withDrawerActions,
withAlertsActions,
)(PaymentMadeDetailActionsBar);

View File

@@ -0,0 +1,23 @@
import React from 'react';
import { DrawerBody } from 'components';
import 'style/components/Drawers/ViewDetail/ViewDetail.scss';
import PaymentMadeDetails from './PaymentMadeDetails';
import { PaymentMadeDetailProvider } from './PaymentMadeDetailProvider';
/**
* Payment made detail content.
*/
export default function PaymentMadeDetailContent({
// #ownProp
paymentMadeId,
}) {
return (
<PaymentMadeDetailProvider paymentMadeId={paymentMadeId}>
<DrawerBody>
<PaymentMadeDetails />
</DrawerBody>
</PaymentMadeDetailProvider>
);
}

View File

@@ -0,0 +1,30 @@
import React from 'react';
import clsx from 'classnames';
import { T, TotalLines, TotalLine } from 'components';
import PaymentDrawerCls from './PaymentMadeDrawer.module.scss';
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
/**
* Payment made - Details panel - Footer.
*/
export default function PaymentMadeDetailFooter() {
const { paymentMade } = usePaymentMadeDetailContext();
return (
<div className={clsx(PaymentDrawerCls.detail_panel_footer)}>
<TotalLines>
<TotalLine
title={<T id={'payment_made.details.subtotal'} />}
value={paymentMade.amount}
className={clsx(PaymentDrawerCls.total_line_subtotal)}
/>
<TotalLine
title={<T id={'payment_made.details.total'} />}
value={paymentMade.formatted_amount}
className={clsx(PaymentDrawerCls.total_line_total)}
/>
</TotalLines>
</div>
);
}

View File

@@ -0,0 +1,53 @@
import React from 'react';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import { defaultTo } from 'lodash';
import { DetailsMenu, DetailItem, FormatDate } from 'components';
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
import PaymentDrawerCls from './PaymentMadeDrawer.module.scss';
/**
* Payment made - detail panel - header.
*/
export default function PaymentMadeDetailHeader() {
const { paymentMade } = usePaymentMadeDetailContext();
return (
<div className={clsx(PaymentDrawerCls.detail_panel_header)}>
<DetailsMenu>
<DetailItem label={intl.get('amount')}>
<h3 class="big-number" children={paymentMade.formatted_amount} />
</DetailItem>
<DetailItem
label={intl.get('payment_made.details.payment_number')}
children={defaultTo(paymentMade.payment_number, '-')}
/>
<DetailItem
label={intl.get('customer_name')}
children={paymentMade.vendor?.display_name}
/>
<DetailItem
label={intl.get('payment_account')}
children={paymentMade.payment_account?.name}
/>
<DetailItem
label={intl.get('payment_date')}
children={<FormatDate value={paymentMade.payment_date} />}
/>
</DetailsMenu>
<DetailsMenu direction={'horizantal'} minLabelSize={'160px'}>
<DetailItem
label={intl.get('description')}
children={defaultTo(paymentMade.statement, '-')}
/>
<DetailItem
label={intl.get('created_at')}
children={<FormatDate value={paymentMade.created_at} />}
/>
</DetailsMenu>
</div>
);
}

View File

@@ -0,0 +1,66 @@
import React from 'react';
import intl from 'react-intl-universal';
import { DrawerHeaderContent, DrawerLoading } from 'components';
import {
useTransactionsByReference,
usePaymentMade,
usePaymentMadeEditPage,
} from 'hooks/query';
const PaymentMadeDetailContext = React.createContext();
/**
* Payment made detail provider.
*/
function PaymentMadeDetailProvider({ paymentMadeId, ...props }) {
// Handle fetch specific payment made details.
const { data: paymentMade, isLoading: isPaymentMadeLoading } =
usePaymentMade(paymentMadeId, {
enabled: !!paymentMadeId,
});
// Handle fetch specific payment made details.
const {
data: { entries: paymentEntries },
isLoading: isPaymentLoading,
} = usePaymentMadeEditPage(paymentMadeId, {
enabled: !!paymentMadeId,
});
// Handle fetch transaction by reference.
const {
data: { transactions },
isLoading: isTransactionLoading,
} = useTransactionsByReference(
{
reference_id: paymentMadeId,
reference_type: 'paymentMade',
},
{ enabled: !!paymentMadeId },
);
//provider.
const provider = {
transactions,
paymentMadeId,
paymentMade,
paymentEntries,
};
const loading =
isTransactionLoading || isPaymentMadeLoading || isPaymentLoading;
return (
<DrawerLoading loading={loading}>
<DrawerHeaderContent
name="payment-made-detail-drawer"
title={intl.get('payment_made_details')}
/>
<PaymentMadeDetailContext.Provider value={provider} {...props} />
</DrawerLoading>
);
}
const usePaymentMadeDetailContext = () =>
React.useContext(PaymentMadeDetailContext);
export { PaymentMadeDetailProvider, usePaymentMadeDetailContext };

View File

@@ -0,0 +1,28 @@
import React from 'react';
import clsx from 'classnames';
import { Card } from 'components';
import PaymentMadeDetailActionsBar from './PaymentMadeDetailActionsBar';
import PaymentMadeDetailHeader from './PaymentMadeDetailHeader';
import PaymentMadeDetailTable from './PaymentMadeDetailTable';
import PaymentMadeDetailFooter from './PaymentMadeDetailFooter';
import PaymentDrawerCls from './PaymentMadeDrawer.module.scss';
/**
* Payment made detail tab.
*/
export default function PaymentMadeDetailTab() {
return (
<div className={clsx(PaymentDrawerCls.detail_panel)}>
<PaymentMadeDetailActionsBar />
<Card>
<PaymentMadeDetailHeader />
<PaymentMadeDetailTable />
<PaymentMadeDetailFooter />
</Card>
</div>
);
}

View File

@@ -0,0 +1,31 @@
import React from 'react';
import clsx from 'classnames';
import { usePaymentMadeEntriesColumns } from './utils';
import { DataTable } from 'components';
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
import PaymentDrawerCls from './PaymentMadeDrawer.module.scss';
/**
* Payment made read-only details table.
*/
export default function PaymentMadeDetailTable() {
// Retrieve payment made entries columns.
const columns = usePaymentMadeEntriesColumns();
// Payment made details context.
const { paymentEntries } = usePaymentMadeDetailContext();
return (
<div className={clsx(PaymentDrawerCls.detail_panel_table)}>
<DataTable
columns={columns}
data={paymentEntries}
className={'table-constrant'}
/>
</div>
);
}

View File

@@ -0,0 +1,35 @@
import React from 'react';
import { Tab } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import clsx from 'classnames';
import { DrawerMainTabs } from 'components';
import PaymentMadeDetailTab from './PaymentMadeDetailTab';
import JournalEntriesTable from '../../JournalEntriesTable/JournalEntriesTable';
import { usePaymentMadeDetailContext } from './PaymentMadeDetailProvider';
import PaymentDrawerCls from './PaymentMadeDrawer.module.scss';
/**
* Payment made - view detail.
*/
export default function PaymentMadeDetails() {
const { transactions } = usePaymentMadeDetailContext();
return (
<div className={clsx(PaymentDrawerCls.root)}>
<DrawerMainTabs defaultSelectedTabId="details">
<Tab
id={'details'}
title={intl.get('details')}
panel={<PaymentMadeDetailTab />}
/>
<Tab
id={'journal_entries'}
title={intl.get('journal_entries')}
panel={<JournalEntriesTable transactions={transactions} />}
/>
</DrawerMainTabs>
</div>
);
}

View File

@@ -0,0 +1,53 @@
.root {}
.detail_panel {
:global .card {
padding: 22px 15px;
}
&_header {
margin-bottom: 30px;
}
&_table {
:global .bigcapital-datatable {
.thead,
.tbody {
.bill_amount,
.amount_due,
.payment_amount {
text-align: right;
}
}
}
}
&_footer {
display: flex;
:global .total_lines {
margin-left: auto;
}
:global .total_lines_line {
.amount,
.title {
width: 180px;
}
.amount {
text-align: right;
}
}
}
.total_line {
&_subtotal {
border-bottom: 1px solid #000;
}
&_total {
border-bottom: 3px double #000;
font-weight: 600;
}
}
}

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