mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
feat(expenses): add expense form top bar.
This commit is contained in:
@@ -11,6 +11,7 @@ import ExpenseFormHeader from './ExpenseFormHeader';
|
||||
import ExpenseFormBody from './ExpenseFormBody';
|
||||
import ExpenseFloatingFooter from './ExpenseFloatingActions';
|
||||
import ExpenseFormFooter from './ExpenseFormFooter';
|
||||
import ExpenseFormTopBar from './ExpenseFormTopBar';
|
||||
|
||||
import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
||||
|
||||
@@ -143,6 +144,7 @@ function ExpenseForm({
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Form>
|
||||
<ExpenseFormTopBar />
|
||||
<ExpenseFormHeader />
|
||||
<ExpenseFormBody />
|
||||
<ExpenseFormFooter />
|
||||
|
||||
@@ -11,20 +11,23 @@ import { PageFormBigNumber } from 'components';
|
||||
|
||||
// Expense form header.
|
||||
export default function ExpenseFormHeader() {
|
||||
const { values } = useFormikContext();
|
||||
const {
|
||||
values: { currency_code, categories },
|
||||
} = useFormikContext();
|
||||
|
||||
// Calculates the expense entries amount.
|
||||
const totalExpenseAmount = useMemo(() => sumBy(values.categories, 'amount'), [
|
||||
values.categories,
|
||||
]);
|
||||
const totalExpenseAmount = useMemo(
|
||||
() => sumBy(categories, 'amount'),
|
||||
[categories],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
|
||||
<ExpenseFormHeaderFields />
|
||||
<PageFormBigNumber
|
||||
label={<T id={'expense_amount'}/>}
|
||||
label={<T id={'expense_amount'} />}
|
||||
amount={totalExpenseAmount}
|
||||
currencyCode={values?.currency_code}
|
||||
currencyCode={currency_code}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import React from 'react';
|
||||
import { InputGroup, FormGroup, Position, Classes } from '@blueprintjs/core';
|
||||
import {
|
||||
InputGroup,
|
||||
FormGroup,
|
||||
ControlGroup,
|
||||
Position,
|
||||
Classes,
|
||||
} from '@blueprintjs/core';
|
||||
import { DateInput } from '@blueprintjs/datetime';
|
||||
import { FastField, ErrorMessage } from 'formik';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
@@ -17,7 +23,9 @@ import {
|
||||
CustomerSelectField,
|
||||
AccountsSelectList,
|
||||
FieldRequiredHint,
|
||||
ExchangeRateInputGroup,
|
||||
Hint,
|
||||
If,
|
||||
} from 'components';
|
||||
import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes';
|
||||
import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
||||
@@ -26,7 +34,15 @@ import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
||||
* Expense form header.
|
||||
*/
|
||||
export default function ExpenseFormHeader() {
|
||||
const { currencies, accounts, customers } = useExpenseFormContext();
|
||||
const {
|
||||
currencies,
|
||||
accounts,
|
||||
customers,
|
||||
isForeignCustomer,
|
||||
baseCurrency,
|
||||
selectCustomer,
|
||||
setSelectCustomer,
|
||||
} = useExpenseFormContext();
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
|
||||
@@ -102,14 +118,23 @@ export default function ExpenseFormHeader() {
|
||||
selectedCurrencyCode={value}
|
||||
onCurrencySelected={(currencyItem) => {
|
||||
form.setFieldValue('currency_code', currencyItem.currency_code);
|
||||
setSelectCustomer(currencyItem);
|
||||
}}
|
||||
defaultSelectText={value}
|
||||
disabled={true}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
|
||||
<If condition={isForeignCustomer}>
|
||||
<ExchangeRateInputGroup
|
||||
fromCurrency={baseCurrency}
|
||||
toCurrency={selectCustomer?.currency_code}
|
||||
name={'exchange_rate'}
|
||||
formGroupProps={{ label: ' ', inline: true }}
|
||||
/>
|
||||
</If>
|
||||
|
||||
<FastField name={'reference_no'}>
|
||||
{({ form, field, meta: { error, touched } }) => (
|
||||
<FormGroup
|
||||
@@ -146,6 +171,7 @@ export default function ExpenseFormHeader() {
|
||||
form.setFieldValue('customer_id', customer.id);
|
||||
}}
|
||||
allowCreate={true}
|
||||
popoverFill={true}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
|
||||
@@ -5,18 +5,22 @@ import 'style/pages/Expense/PageForm.scss';
|
||||
|
||||
import ExpenseForm from './ExpenseForm';
|
||||
import { ExpenseFormPageProvider } from './ExpenseFormPageProvider';
|
||||
|
||||
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||
import { compose } from 'utils';
|
||||
/**
|
||||
* Expense page form.
|
||||
*/
|
||||
export default function ExpenseFormPage() {
|
||||
function ExpenseFormPage({
|
||||
// #withCurrentOrganization
|
||||
organization: { base_currency },
|
||||
}) {
|
||||
const { id } = useParams();
|
||||
const expenseId = parseInt(id, 10);
|
||||
|
||||
return (
|
||||
<ExpenseFormPageProvider expenseId={expenseId}>
|
||||
<ExpenseFormPageProvider expenseId={expenseId} baseCurrency={base_currency}>
|
||||
<ExpenseForm />
|
||||
</ExpenseFormPageProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withCurrentOrganization())(ExpenseFormPage);
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import React, { createContext } from 'react';
|
||||
import { isEqual, isUndefined } from 'lodash';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import { Features } from 'common';
|
||||
import { useFeatureCan } from 'hooks/state';
|
||||
import {
|
||||
useCurrencies,
|
||||
useCustomers,
|
||||
useExpense,
|
||||
useAccounts,
|
||||
useBranches,
|
||||
useCreateExpense,
|
||||
useEditExpense,
|
||||
} from 'hooks/query';
|
||||
@@ -14,7 +18,11 @@ const ExpenseFormPageContext = createContext();
|
||||
/**
|
||||
* Accounts chart data provider.
|
||||
*/
|
||||
function ExpenseFormPageProvider({ expenseId, ...props }) {
|
||||
function ExpenseFormPageProvider({ query, expenseId, baseCurrency, ...props }) {
|
||||
// Features guard.
|
||||
const { featureCan } = useFeatureCan();
|
||||
const isBranchFeatureCan = featureCan(Features.Branches);
|
||||
|
||||
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
||||
|
||||
// Fetches customers list.
|
||||
@@ -24,12 +32,16 @@ function ExpenseFormPageProvider({ expenseId, ...props }) {
|
||||
} = useCustomers();
|
||||
|
||||
// Fetch the expense details.
|
||||
const { data: expense, isLoading: isExpenseLoading } = useExpense(
|
||||
expenseId,
|
||||
{
|
||||
enabled: !!expenseId,
|
||||
},
|
||||
);
|
||||
const { data: expense, isLoading: isExpenseLoading } = useExpense(expenseId, {
|
||||
enabled: !!expenseId,
|
||||
});
|
||||
|
||||
// Fetches the branches list.
|
||||
const {
|
||||
data: branches,
|
||||
isLoading: isBranchesLoading,
|
||||
isSuccess: isBranchesSuccess,
|
||||
} = useBranches(query, { enabled: isBranchFeatureCan });
|
||||
|
||||
// Fetch accounts list.
|
||||
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
|
||||
@@ -40,29 +52,40 @@ function ExpenseFormPageProvider({ expenseId, ...props }) {
|
||||
|
||||
// Submit form payload.
|
||||
const [submitPayload, setSubmitPayload] = React.useState({});
|
||||
const [selectCustomer, setSelectCustomer] = React.useState(null);
|
||||
|
||||
//
|
||||
// Detarmines whether the form in new mode.
|
||||
const isNewMode = !expenseId;
|
||||
|
||||
// Determines whether the foreign customer.
|
||||
const isForeignCustomer =
|
||||
!isEqual(selectCustomer?.currency_code, baseCurrency) &&
|
||||
!isUndefined(selectCustomer?.currency_code);
|
||||
|
||||
// Provider payload.
|
||||
const provider = {
|
||||
isNewMode,
|
||||
isForeignCustomer,
|
||||
expenseId,
|
||||
submitPayload,
|
||||
selectCustomer,
|
||||
|
||||
currencies,
|
||||
customers,
|
||||
expense,
|
||||
accounts,
|
||||
branches,
|
||||
|
||||
isCurrenciesLoading,
|
||||
isExpenseLoading,
|
||||
isCustomersLoading,
|
||||
isAccountsLoading,
|
||||
isBranchesSuccess,
|
||||
|
||||
createExpenseMutate,
|
||||
editExpenseMutate,
|
||||
setSubmitPayload,
|
||||
setSelectCustomer,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
68
src/containers/Expenses/ExpenseForm/ExpenseFormTopBar.js
Normal file
68
src/containers/Expenses/ExpenseForm/ExpenseFormTopBar.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Button, Alignment, NavbarGroup, Classes } from '@blueprintjs/core';
|
||||
import { useSetPrimaryBranchToForm } from './utils';
|
||||
import { useFeatureCan } from 'hooks/state';
|
||||
import {
|
||||
Icon,
|
||||
BranchSelect,
|
||||
FeatureCan,
|
||||
FormTopbar,
|
||||
DetailsBarSkeletonBase,
|
||||
} from 'components';
|
||||
import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
||||
import { Features } from 'common';
|
||||
|
||||
/**
|
||||
* Expenses form topbar.
|
||||
* @returns
|
||||
*/
|
||||
export default function ExpenseFormTopBar() {
|
||||
// Features guard.
|
||||
const { featureCan } = useFeatureCan();
|
||||
|
||||
// Sets the primary branch to form.
|
||||
useSetPrimaryBranchToForm();
|
||||
|
||||
// Can't display the navigation bar if branches feature is not enabled.
|
||||
if (!featureCan(Features.Branches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormTopbar>
|
||||
<NavbarGroup align={Alignment.LEFT}>
|
||||
<FeatureCan feature={Features.Branches}>
|
||||
<ExpenseFormSelectBranch />
|
||||
</FeatureCan>
|
||||
</NavbarGroup>
|
||||
</FormTopbar>
|
||||
);
|
||||
}
|
||||
|
||||
function ExpenseFormSelectBranch() {
|
||||
// Invoice form context.
|
||||
const { branches, isBranchesLoading } = useExpenseFormContext();
|
||||
|
||||
return isBranchesLoading ? (
|
||||
<DetailsBarSkeletonBase className={Classes.SKELETON} />
|
||||
) : (
|
||||
<BranchSelect
|
||||
name={'branch_id'}
|
||||
branches={branches}
|
||||
input={ExpenseBranchSelectButton}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ExpenseBranchSelectButton({ label }) {
|
||||
return (
|
||||
<Button
|
||||
text={intl.get('invoice.branch_button.label', { label })}
|
||||
minimal={true}
|
||||
small={true}
|
||||
icon={<Icon icon={'branch-16'} iconSize={16} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
import React from 'react';
|
||||
import { AppToaster } from 'components';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { useFormikContext } from 'formik';
|
||||
import moment from 'moment';
|
||||
import intl from 'react-intl-universal';
|
||||
import * as R from 'ramda';
|
||||
import { first } from 'lodash';
|
||||
import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
||||
|
||||
import {
|
||||
defaultFastFieldShouldUpdate,
|
||||
transformToForm,
|
||||
repeatValue,
|
||||
ensureEntriesHasEmptyLine,
|
||||
orderingLinesIndexes
|
||||
orderingLinesIndexes,
|
||||
} from 'utils';
|
||||
|
||||
const ERROR = {
|
||||
@@ -55,6 +60,8 @@ export const defaultExpense = {
|
||||
reference_no: '',
|
||||
currency_code: '',
|
||||
publish: '',
|
||||
branch_id: '',
|
||||
exchange_rate: 1,
|
||||
categories: [...repeatValue(defaultExpenseEntry, MIN_LINES_NUMBER)],
|
||||
};
|
||||
|
||||
@@ -106,15 +113,14 @@ export const accountsFieldShouldUpdate = (newProps, oldProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Filter expense entries that has no amount or expense account.
|
||||
*/
|
||||
export const filterNonZeroEntries = (categories) => {
|
||||
export const filterNonZeroEntries = (categories) => {
|
||||
return categories.filter(
|
||||
(category) => category.amount && category.expense_account_id,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the form values to request body.
|
||||
@@ -127,3 +133,18 @@ export const transformFormValuesToRequest = (values) => {
|
||||
categories: R.compose(orderingLinesIndexes)(categories),
|
||||
};
|
||||
};
|
||||
|
||||
export const useSetPrimaryBranchToForm = () => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const { branches, isBranchesSuccess } = useExpenseFormContext();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isBranchesSuccess) {
|
||||
const primaryBranch = branches.find((b) => b.primary) || first(branches);
|
||||
|
||||
if (primaryBranch) {
|
||||
setFieldValue('branch_id', primaryBranch.id);
|
||||
}
|
||||
}
|
||||
}, [isBranchesSuccess, setFieldValue, branches]);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user