feat: expense drawer.

This commit is contained in:
elforjani3
2021-04-27 16:16:31 +02:00
parent 571d9eb2fd
commit 61e0ad969f
10 changed files with 325 additions and 11 deletions

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
import React from 'react';
import { If, Money } from 'components';
export default function ExpenseDrawerFooter({
expense: { total_amount, currency_code },
}) {
return (
<div className="expense-drawer__content--footer">
<div className="wrapper">
<div>
<span>Sub Total</span>
<p>{<Money amount={total_amount} currency={currency_code} />}</p>
</div>
<div>
<span>Total</span>
<p>{<Money amount={total_amount} currency={currency_code} />}</p>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,68 @@
import React from 'react';
import moment from 'moment';
import { If, Money } from 'components';
import { FormattedMessage as T } from 'react-intl';
/**
* Expense drawer content.
*/
export default function ExpenseDrawerHeader({
expense: {
total_amount,
payment_account: { name },
payment_date,
currency_code,
reference_no,
description,
published_at,
},
}) {
return (
<div className={'expense-drawer__content--header'}>
<div className={'info'}>
<span>
<T id={'full_amount'} />
</span>
<p className="balance">
{<Money amount={total_amount} currency={currency_code} />}
</p>
</div>
<div className={'info'}>
<span>
<T id={'date'} />
</span>
<p>{moment(payment_date).format('YYYY MMM DD')}</p>
</div>
<div className={'info'}>
<span>
<T id={'payment_account_'} />
</span>
<p>{name}</p>
</div>
<div className={'info'}>
<span>
<T id={'currency'} />
</span>
<p>{currency_code}</p>
</div>
<div className={'info'}>
<span>
<T id={'reference_no'} />
</span>
<p>{reference_no}</p>
</div>
<div className={'info'}>
<span>
<T id={'published_at'} />
</span>
<p>{moment(published_at).format('YYYY MMM DD')}</p>
</div>
<div className={'info'}>
<span>
<T id={'description'} />
</span>
<p>{description}</p>
</div>
</div>
);
}

View File

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

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { formatMessage } from 'services/intl';
import { DataTable, Money } from 'components';
/**
* Expense details table.
*/
export default function ExpenseDrawerTable({
expense: { currency_code, categories },
}) {
const columns = React.useMemo(
() => [
{
Header: formatMessage({ id: 'Expense account' }),
accessor: 'expense_account.name',
width: 110,
},
{
Header: formatMessage({ id: 'Amount' }),
accessor: ({ amount }) => (
<Money amount={amount} currency={currency_code} />
),
width: 100,
},
{
Header: formatMessage({ id: 'description' }),
accessor: 'description',
width: 110,
},
],
[],
);
return (
<div className="expense-drawer__content--table">
<DataTable columns={columns} data={categories} />
</div>
);
}

View File

@@ -0,0 +1,36 @@
import React, { lazy } from 'react';
import { Drawer, DrawerSuspense } from 'components';
import withDrawers from 'containers/Drawer/withDrawers';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
const ExpenseDrawerContent = lazy(() => import('./ExpenseDrawerContent'));
/**
* Expense drawer.
*/
function ExpenseDrawer({
name,
//#withDrawer
isOpen,
payload: { expenseId, title },
closeDrawer,
}) {
// Handle close drawer.
const handleDrawerClose = () => {
closeDrawer(name);
};
return (
<Drawer isOpen={isOpen} title={title} isClose={handleDrawerClose}>
<DrawerSuspense>
<ExpenseDrawerContent expenseId={expenseId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers(), withDrawerActions)(ExpenseDrawer);

View File

@@ -12,6 +12,7 @@ import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withExpensesActions from './withExpensesActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { ActionsMenu, useExpensesTableColumns } from './components';
@@ -22,6 +23,9 @@ function ExpensesDataTable({
// #withExpensesActions
setExpensesTableState,
// #withDrawerActions
openDrawer,
// #withAlertsActions
openAlert,
}) {
@@ -32,7 +36,7 @@ function ExpensesDataTable({
isExpensesLoading,
isExpensesFetching,
isEmptyStatus
isEmptyStatus,
} = useExpensesListContext();
const history = useHistory();
@@ -67,6 +71,14 @@ function ExpensesDataTable({
openAlert('expense-delete', { expenseId: expense.id });
};
// Handle view detail expense.
const handleViewDetailExpense = ({ id }) => {
openDrawer('expense-drawer', {
expenseId: id,
title: `Expense`,
});
};
// Display empty status instead of the table.
if (isEmptyStatus) {
return <ExpensesEmptyStatus />;
@@ -76,34 +88,27 @@ function ExpensesDataTable({
<DataTable
columns={columns}
data={expenses}
loading={isExpensesLoading}
headerLoading={isExpensesLoading}
progressBarLoading={isExpensesFetching}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onFetchData={handleFetchData}
pagination={true}
manualSortBy={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onPublish: handlePublishExpense,
onDelete: handleDeleteExpense,
onEdit: handleEditExpense
onEdit: handleEditExpense,
onViewDetails: handleViewDetailExpense,
}}
/>
);
@@ -112,5 +117,6 @@ function ExpensesDataTable({
export default compose(
withDashboardActions,
withAlertsActions,
withDrawerActions,
withExpensesActions,
)(ExpensesDataTable);

View File

@@ -40,13 +40,14 @@ export function DescriptionAccessor(row) {
*/
export function ActionsMenu({
row: { original },
payload: { onPublish, onEdit, onDelete },
payload: { onPublish, onEdit, onDelete, onViewDetails },
}) {
return (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })}
onClick={safeCallback(onViewDetails, original)}
/>
<MenuDivider />
<If condition={!original.is_published}>