fix: FastField re-rendering.

fix: Allocate landed cost dialog.
This commit is contained in:
a.bouhuolia
2021-07-26 19:45:16 +02:00
parent 77d987ef1f
commit 9baf81f803
77 changed files with 1046 additions and 364 deletions

View File

@@ -0,0 +1,5 @@
import React from 'react';
export default function Card({ children }) {
return <div class="card">{children}</div>;
}

View File

@@ -4,14 +4,26 @@ import { CLASSES } from 'common/classes';
import { DataTable, If } from 'components';
import 'style/components/DataTable/DataTableEditable.scss';
/**
* Editable datatable.
*/
export default function DatatableEditable({
totalRow = false,
actions,
name,
className,
...tableProps
}) {
return (
<div className={classNames(CLASSES.DATATABLE_EDITOR, className)}>
<div
className={classNames(
CLASSES.DATATABLE_EDITOR,
{
[`${CLASSES.DATATABLE_EDITOR}--${name}`]: name,
},
className,
)}
>
<DataTable {...tableProps} />
<If condition={actions}>

View File

@@ -0,0 +1,29 @@
import React from 'react';
import className from 'classname';
/**
* Details menu.
*/
export function DetailsMenu({ children, vertical = false }) {
return (
<div
className={className('details-menu', {
'is-vertical': vertical,
})}
>
{children}
</div>
);
}
/**
* Detail item.
*/
export function DetailItem({ label, children }) {
return (
<div class="detail-item">
<div class="detail-item__label">{label}</div>
<div class="detail-item__content">{children}</div>
</div>
);
}

View File

@@ -1,9 +1,14 @@
import React from 'react';
import { Position, Drawer } from '@blueprintjs/core';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import 'style/components/Drawer.scss';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
/**
* Drawer component.
*/
function DrawerComponent(props) {
const { name, children, onClose, closeDrawer } = props;

View File

@@ -57,6 +57,7 @@ import Postbox from './Postbox';
import AccountsSuggestField from './AccountsSuggestField';
import MaterialProgressBar from './MaterialProgressBar';
import { MoneyFieldCell } from './DataTableCells';
import Card from './Card';
import { ItemsMultiSelect } from './Items';
@@ -127,5 +128,6 @@ export {
AccountsSuggestField,
MaterialProgressBar,
MoneyFieldCell,
ItemsMultiSelect
ItemsMultiSelect,
Card
};

View File

@@ -67,4 +67,4 @@ export const useManualJournalsColumns = () => {
],
[],
);
};
};

View File

@@ -3,16 +3,28 @@ import { FastField } from 'formik';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import MakeJournalEntriesTable from './MakeJournalEntriesTable';
import { defaultEntry, MIN_LINES_NUMBER } from './utils';
import { entriesFieldShouldUpdate, defaultEntry, MIN_LINES_NUMBER } from './utils';
import { useMakeJournalFormContext } from './MakeJournalProvider';
/**
* Make journal entries field.
*/
export default function MakeJournalEntriesField() {
const { accounts, contacts } = useMakeJournalFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<FastField name={'entries'}>
{({ form:{values ,setFieldValue}, field: { value }, meta: { error, touched } }) => (
<FastField
name={'entries'}
contacts={contacts}
accounts={accounts}
shouldUpdate={entriesFieldShouldUpdate}
>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<MakeJournalEntriesTable
onChange={(entries) => {
setFieldValue('entries', entries);

View File

@@ -29,7 +29,10 @@ import {
import withSettings from 'containers/Settings/withSettings';
import { useMakeJournalFormContext } from './MakeJournalProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { useObserveJournalNoSettings } from './utils';
import {
currenciesFieldShouldUpdate,
useObserveJournalNoSettings,
} from './utils';
/**
* Make journal entries header.
*/
@@ -182,7 +185,11 @@ function MakeJournalEntriesHeader({
</FastField>
{/*------------ Currency -----------*/}
<FastField name={'currency_code'}>
<FastField
name={'currency_code'}
currencies={currencies}
shouldUpdate={currenciesFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'currency'} />}

View File

@@ -1,13 +1,13 @@
import React from 'react';
import { Intent } from '@blueprintjs/core';
import { sumBy, setWith, toSafeInteger, get, values } from 'lodash';
import { sumBy, setWith, toSafeInteger, get } from 'lodash';
import moment from 'moment';
import {
transactionNumber,
updateTableRow,
repeatValue,
transformToForm,
defaultFastFieldShouldUpdate,
} from 'utils';
import { AppToaster } from 'components';
import intl from 'react-intl-universal';
@@ -123,17 +123,17 @@ export const transformErrors = (resErrors, { setErrors, errors }) => {
setEntriesErrors(error.indexes, 'contact_id', 'error');
}
if ((error = getError(ERROR.ENTRIES_SHOULD_ASSIGN_WITH_CONTACT))) {
if (error.meta.find(meta => meta.contact_type === 'customer')) {
if (error.meta.find((meta) => meta.contact_type === 'customer')) {
toastMessages.push(
intl.get('receivable_accounts_should_assign_with_customers'),
);
}
if (error.meta.find(meta => meta.contact_type === 'vendor')) {
if (error.meta.find((meta) => meta.contact_type === 'vendor')) {
toastMessages.push(
intl.get('payable_accounts_should_assign_with_vendors'),
);
}
const indexes = error.meta.map((meta => meta.indexes)).flat();
const indexes = error.meta.map((meta) => meta.indexes).flat();
setEntriesErrors(indexes, 'contact_id', 'error');
}
if ((error = getError(ERROR.JOURNAL_NUMBER_ALREADY_EXISTS))) {
@@ -159,7 +159,28 @@ export const useObserveJournalNoSettings = (prefix, nextNumber) => {
const { setFieldValue } = useFormikContext();
React.useEffect(() => {
const journalNo = transactionNumber(prefix, nextNumber);
const journalNo = transactionNumber(prefix, nextNumber);
setFieldValue('journal_number', journalNo);
}, [setFieldValue, prefix, nextNumber]);
};
/**
* Detarmines entries fast field should update.
*/
export const entriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.contacts !== oldProps.contacts ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines currencies fast field should update.
*/
export const currenciesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.currencies !== oldProps.currencies ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -56,7 +56,7 @@ function BillTransactionDeleteAlert({
onConfirm={handleConfirmLandedCostDelete}
loading={isLoading}
>
<p>{/* <T id={''}/> */}</p>
<p><T id={`Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?`}/></p>
</Alert>
);
}

View File

@@ -3,6 +3,7 @@ import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import moment from 'moment';
import { sumBy } from 'lodash';
import 'style/pages/AllocateLandedCost/AllocateLandedCostForm.scss';
@@ -48,6 +49,7 @@ function AllocateLandedCostForm({
cost: '',
})),
};
const amount = sumBy(initialValues.items, 'amount');
// Handle form submit.
const handleFormSubmit = (values, { setSubmitting }) => {
@@ -84,9 +86,12 @@ function AllocateLandedCostForm({
createLandedCostMutate([billId, form]).then(onSuccess).catch(onError);
};
// Computed validation schema.
const validationSchema = AllocateLandedCostFormSchema(amount);
return (
<Formik
validationSchema={AllocateLandedCostFormSchema}
validationSchema={validationSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
component={AllocateLandedCostFormContent}

View File

@@ -1,19 +1,18 @@
import * as Yup from 'yup';
import intl from 'react-intl-universal';
const Schema = Yup.object().shape({
transaction_type: Yup.string().label(intl.get('transaction_type')),
transaction_date: Yup.date().label(intl.get('transaction_date')),
transaction_id: Yup.string().label(intl.get('transaction_number')),
transaction_entry_id: Yup.string().label(intl.get('transaction_line')),
amount: Yup.number().label(intl.get('amount')),
allocation_method: Yup.string().trim(),
items: Yup.array().of(
Yup.object().shape({
entry_id: Yup.number().nullable(),
cost: Yup.number().nullable(),
}),
),
});
export const AllocateLandedCostFormSchema = Schema;
export const AllocateLandedCostFormSchema = (minAmount) =>
Yup.object().shape({
transaction_type: Yup.string().label(intl.get('transaction_type')),
transaction_date: Yup.date().label(intl.get('transaction_date')),
transaction_id: Yup.string().label(intl.get('transaction_number')),
transaction_entry_id: Yup.string().label(intl.get('transaction_line')),
amount: Yup.number().max(minAmount).label(intl.get('amount')),
allocation_method: Yup.string().trim(),
items: Yup.array().of(
Yup.object().shape({
entry_id: Yup.number().nullable(),
cost: Yup.number().nullable(),
}),
),
});

View File

@@ -17,7 +17,7 @@ import allocateLandedCostType from 'common/allocateLandedCostType';
import { useLandedCostTransaction } from 'hooks/query';
import AllocateLandedCostFormBody from './AllocateLandedCostFormBody';
import { getEntriesByTransactionId } from './utils';
import { getEntriesByTransactionId, allocateCostToEntries } from './utils';
/**
* Allocate landed cost form fields.
@@ -30,10 +30,10 @@ export default function AllocateLandedCostFormFields() {
} = useLandedCostTransaction(values.transaction_type);
// Retrieve entries of the given transaction id.
const transactionEntries = React.useMemo(() => getEntriesByTransactionId(
transactions,
values.transaction_id,
), [transactions, values.transaction_id]);
const transactionEntries = React.useMemo(
() => getEntriesByTransactionId(transactions, values.transaction_id),
[transactions, values.transaction_id],
);
return (
<div className={Classes.DIALOG_BODY}>
@@ -56,6 +56,8 @@ export default function AllocateLandedCostFormFields() {
items={allocateLandedCostType}
onItemSelect={(type) => {
setFieldValue('transaction_type', type.value);
setFieldValue('transaction_id', '');
setFieldValue('transaction_entry_id', '');
}}
filterable={false}
selectedItem={value}
@@ -82,13 +84,14 @@ export default function AllocateLandedCostFormFields() {
items={transactions}
onItemSelect={({ id }) => {
form.setFieldValue('transaction_id', id);
form.setFieldValue('transaction_entry_id', '');
}}
filterable={false}
selectedItem={value}
selectedItemProp={'id'}
textProp={'name'}
labelProp={'id'}
defaultText={intl.get('select_transaction')}
defaultText={intl.get('Select transaction')}
popoverProps={{ minimal: true }}
/>
</FormGroup>
@@ -112,15 +115,21 @@ export default function AllocateLandedCostFormFields() {
<ListSelect
items={transactionEntries}
onItemSelect={({ id, amount }) => {
form.setFieldValue('amount', amount)
const { items, allocation_method } = form.values;
form.setFieldValue('amount', amount);
form.setFieldValue('transaction_entry_id', id);
form.setFieldValue(
'items',
allocateCostToEntries(amount, allocation_method, items),
);
}}
filterable={false}
selectedItem={value}
selectedItemProp={'id'}
textProp={'name'}
labelProp={'id'}
defaultText={intl.get('select_transaction')}
defaultText={intl.get('Select transaction entry')}
popoverProps={{ minimal: true }}
/>
</FormGroup>
@@ -138,13 +147,24 @@ export default function AllocateLandedCostFormFields() {
className={'form-group--amount'}
inline={true}
>
<InputGroup {...field} />
<InputGroup
{...field}
onBlur={(e) => {
const amount = e.target.value;
const { allocation_method, items } = form.values;
form.setFieldValue(
'items',
allocateCostToEntries(amount, allocation_method, items),
);
}}
/>
</FormGroup>
)}
</FastField>
{/*------------ Allocation method -----------*/}
<FastField name={'allocation_method'}>
<Field name={'allocation_method'}>
{({ form, field: { value }, meta: { touched, error } }) => (
<FormGroup
medium={true}
@@ -157,7 +177,13 @@ export default function AllocateLandedCostFormFields() {
>
<RadioGroup
onChange={handleStringChange((_value) => {
const { amount, items, allocation_method } = form.values;
form.setFieldValue('allocation_method', _value);
form.setFieldValue(
'items',
allocateCostToEntries(amount, allocation_method, items),
);
})}
selectedValue={value}
inline={true}
@@ -167,7 +193,7 @@ export default function AllocateLandedCostFormFields() {
</RadioGroup>
</FormGroup>
)}
</FastField>
</Field>
{/*------------ Allocate Landed cost Table -----------*/}
<AllocateLandedCostFormBody />

View File

@@ -1,3 +1,5 @@
import { sumBy, round } from 'lodash';
import * as R from 'ramda';
/**
* Retrieve transaction entries of the given transaction id.
*/
@@ -5,3 +7,56 @@ export function getEntriesByTransactionId(transactions, id) {
const transaction = transactions.find((trans) => trans.id === id);
return transaction ? transaction.entries : [];
}
export function allocateCostToEntries(total, allocateType, entries) {
return R.compose(
R.when(
R.always(allocateType === 'value'),
R.curry(allocateCostByValue)(total),
),
R.when(
R.always(allocateType === 'quantity'),
R.curry(allocateCostByQuantity)(total),
),
)(entries);
}
/**
* Allocate total cost on entries on value.
* @param {*} entries
* @param {*} total
* @returns
*/
export function allocateCostByValue(total, entries) {
const totalAmount = sumBy(entries, 'amount');
const _entries = entries.map((entry) => ({
...entry,
percentageOfValue: entry.amount / totalAmount,
}));
return _entries.map((entry) => ({
...entry,
cost: round(entry.percentageOfValue * total, 2),
}));
}
/**
* Allocate total cost on entries by quantity.
* @param {*} entries
* @param {*} total
* @returns
*/
export function allocateCostByQuantity(total, entries) {
const totalQuantity = sumBy(entries, 'quantity');
const _entries = entries.map((entry) => ({
...entry,
percentageOfQuantity: entry.quantity / totalQuantity,
}));
return _entries.map((entry) => ({
...entry,
cost: round(entry.percentageOfQuantity * total, 2),
}));
}

View File

@@ -1,4 +1,7 @@
import React from 'react';
import 'style/components/Drawers/AccountDrawer.scss';
import { AccountDrawerProvider } from './AccountDrawerProvider';
import AccountDrawerDetails from './AccountDrawerDetails';

View File

@@ -5,8 +5,6 @@ import AccountDrawerHeader from './AccountDrawerHeader';
import AccountDrawerTable from './AccountDrawerTable';
import { useAccountDrawerContext } from './AccountDrawerProvider';
import 'style/components/Drawer/AccountDrawer.scss';
/**
* Account view details.
*/

View File

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

View File

@@ -1,4 +1,7 @@
import React from 'react';
import 'style/components/Drawers/BillDrawer.scss';
import { BillDrawerProvider } from './BillDrawerProvider';
import BillDrawerDetails from './BillDrawerDetails';
import BillDrawerAlerts from './BillDrawerAlerts';

View File

@@ -4,8 +4,6 @@ import intl from 'react-intl-universal';
import LocatedLandedCostTable from './LocatedLandedCostTable';
import 'style/components/Drawer/BillDrawer.scss';
/**
* Bill view details.
*/

View File

@@ -18,7 +18,9 @@ function BillDrawerProvider({ billId, ...props }) {
//provider.
const provider = {
transactions,
billId,
};
return (
<DashboardInsider loading={isLandedCostLoading}>
<DrawerHeaderContent

View File

@@ -1,11 +1,17 @@
import React from 'react';
import { DataTable } from 'components';
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.
@@ -13,25 +19,73 @@ import { compose } from 'utils';
function LocatedLandedCostTable({
// #withAlertsActions
openAlert,
// #withDialogActions
openDialog,
// #withDrawerActions
openDrawer,
}) {
const columns = useLocatedLandedCostColumns();
const { transactions } = useBillDrawerContext();
const { transactions, billId } = useBillDrawerContext();
// Handle the transaction delete action.
const handleDeleteTransaction = ({ id }) => {
openAlert('transaction-delete', { BillId: 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 (
<DataTable
columns={columns}
data={transactions}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeleteTransaction,
}}
/>
<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)(LocatedLandedCostTable);
export default compose(
withAlertsActions,
withDialogActions,
withDrawerActions,
)(LocatedLandedCostTable);

View File

@@ -3,7 +3,6 @@ import intl from 'react-intl-universal';
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
import { safeCallback } from 'utils';
import { Icon } from 'components';
/**
* Actions menu.
*/
@@ -20,22 +19,57 @@ export function ActionsMenu({ row: { original }, payload: { onDelete } }) {
);
}
export function useLocatedLandedCostColumns() {
return React.useMemo(() => [
{
Header: intl.get('name'),
accessor: 'description',
width: 150,
},
{
Header: intl.get('amount'),
accessor: 'amount',
width: 100,
},
{
Header: intl.get('allocation_method'),
accessor: 'allocation_method',
width: 100,
},
]);
/**
* 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

@@ -1,4 +1,7 @@
import React from 'react';
import 'style/components/Drawers/ViewDetails.scss';
import { ExpenseDrawerProvider } from './ExpenseDrawerProvider';
import ExpenseDrawerDetails from './ExpenseDrawerDetails';

View File

@@ -4,7 +4,6 @@ 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.

View File

@@ -1,4 +1,7 @@
import React from 'react';
import 'style/components/Drawers/ViewDetails.scss';
import { ManualJournalDrawerProvider } from './ManualJournalDrawerProvider';
import ManualJournalDrawerDetails from './ManualJournalDrawerDetails';

View File

@@ -6,8 +6,6 @@ import ManualJournalDrawerFooter from './ManualJournalDrawerFooter';
import { useManualJournalDrawerContext } from 'containers/Drawers/ManualJournalDrawer/ManualJournalDrawerProvider';
import 'style/components/Drawer/ViewDetails.scss';
/**
* Manual journal view details.
*/

View File

@@ -1,7 +1,7 @@
import React from 'react';
import intl from 'react-intl-universal';
import { useJournal } from 'hooks/query';
import { DashboardInsider, DrawerHeaderContent } from 'components';
import intl from 'react-intl-universal';
const ManualJournalDrawerContext = React.createContext();

View File

@@ -69,6 +69,7 @@ export default function ManualJournalDrawerTable({
return (
<div className="journal-drawer__content--table">
<DataTable columns={columns} data={entries} />
<If condition={description}>
<p className={'desc'}>
<b>Description</b>: {description}

View File

@@ -1,11 +1,13 @@
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';
import 'style/components/Drawer/DrawerTemplate.scss';
function PaperTemplate({ labels: propLabels, paperData, entries }) {
const labels = {

View File

@@ -1,9 +1,11 @@
import React from 'react';
import 'style/components/Drawers/DrawerTemplate.scss';
import PaymentPaperTemplateHeader from './PaymentPaperTemplateHeader';
import PaymentPaperTemplateTable from './PaymentPaperTemplateTable';
import intl from 'react-intl-universal';
import 'style/components/Drawer/DrawerTemplate.scss';
export default function PaymentPaperTemplate({
labels: propLabels,

View File

@@ -30,6 +30,7 @@ function ItemsEntriesTable({
linesNumber,
currencyCode,
itemType, // sellable or purchasable
landedCost = false
}) {
const [rows, setRows] = React.useState(initialEntries);
const [rowItem, setRowItem] = React.useState(null);
@@ -94,7 +95,7 @@ function ItemsEntriesTable({
}, [entries, rows]);
// Editiable items entries columns.
const columns = useEditableItemsEntriesColumns();
const columns = useEditableItemsEntriesColumns({ landedCost });
// Handles the editor data update.
const handleUpdateData = useCallback(

View File

@@ -10,6 +10,7 @@ import {
ItemsListCell,
PercentFieldCell,
NumericInputCell,
CheckBoxFieldCell,
} from 'components/DataTableCells';
/**
@@ -90,27 +91,18 @@ export function IndexTableCell({ row: { index } }) {
return <span>{index + 1}</span>;
}
/**
* Landed cost cell.
*/
const LandedCostCell = ({
row: { index },
column: { id },
cell: { value: initialValue },
data,
payload,
}) => {
return <Checkbox minimal={true} className="ml2" />;
};
/**
* Landed cost header cell.
*/
const LandedCostHeaderCell = () => {
return (
<>
<T id={'cost'} />
<Hint content={''} />
<T id={'Landed'} />
<Hint
content={
'This options allows you to be able to add additional cost eg. freight then allocate cost to the items in your bills.'
}
/>
</>
);
};
@@ -118,7 +110,7 @@ const LandedCostHeaderCell = () => {
/**
* Retrieve editable items entries columns.
*/
export function useEditableItemsEntriesColumns() {
export function useEditableItemsEntriesColumns({ landedCost }) {
return React.useMemo(
() => [
{
@@ -182,14 +174,19 @@ export function useEditableItemsEntriesColumns() {
width: 100,
className: 'total',
},
{
Header: '',
accessor: 'landed_cost',
Cell: LandedCostCell,
width: 70,
disableSortBy: true,
disableResizing: true,
},
...(landedCost
? [
{
Header: LandedCostHeaderCell,
accessor: 'landed_cost',
Cell: CheckBoxFieldCell,
width: 100,
disableSortBy: true,
disableResizing: true,
className: 'landed-cost',
},
]
: []),
{
Header: '',
accessor: 'action',

View File

@@ -1,14 +1,22 @@
import { FastField } from 'formik';
import React from 'react';
import ExpenseFormEntriesTable from './ExpenseFormEntriesTable';
import { defaultExpenseEntry } from './utils';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
import { defaultExpenseEntry, accountsFieldShouldUpdate } from './utils';
/**
* Expense form entries field.
*/
export default function ExpenseFormEntriesField({ linesNumber = 4 }) {
// Expense form context.
const { accounts } = useExpenseFormContext();
return (
<FastField name={'categories'}>
<FastField
name={'categories'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({
form: { values, setFieldValue },
field: { value },

View File

@@ -22,12 +22,13 @@ export default function ExpenseFormEntriesTable({
error,
onChange,
currencyCode,
landedCost = true,
}) {
// Expense form context.
const { accounts } = useExpenseFormContext();
// Memorized data table columns.
const columns = useExpenseFormTableColumns();
const columns = useExpenseFormTableColumns({ landedCost });
// Handles update datatable data.
const handleUpdateData = useCallback(
@@ -61,6 +62,7 @@ export default function ExpenseFormEntriesTable({
return (
<DataTableEditable
name={'expense-form'}
columns={columns}
data={entries}
sticky={true}

View File

@@ -11,6 +11,7 @@ import {
inputIntent,
handleDateChange,
} from 'utils';
import { customersFieldShouldUpdate, accountsFieldShouldUpdate } from './utils';
import {
CurrencySelectList,
ContactSelecetList,
@@ -51,7 +52,11 @@ export default function ExpenseFormHeader() {
)}
</FastField>
<FastField name={'payment_account_id'}>
<FastField
name={'payment_account_id'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'payment_account'} />}
@@ -118,7 +123,11 @@ export default function ExpenseFormHeader() {
)}
</FastField>
<FastField name={'customer_id'}>
<FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer'} />}

View File

@@ -56,8 +56,12 @@ const ActionsCellRenderer = ({
const LandedCostHeaderCell = () => {
return (
<>
<T id={'cost'} />
<Hint content={''} />
<T id={'Landed'} />
<Hint
content={
'This options allows you to be able to add additional cost eg. freight then allocate cost to the items in your bills.'
}
/>
</>
);
};
@@ -87,7 +91,7 @@ function ExpenseAccountFooterCell() {
/**
* Retrieve expense form table entries columns.
*/
export function useExpenseFormTableColumns() {
export function useExpenseFormTableColumns({ landedCost }) {
return React.useMemo(
() => [
{
@@ -127,15 +131,19 @@ export function useExpenseFormTableColumns() {
className: 'description',
width: 100,
},
{
Header: LandedCostHeaderCell,
accessor: 'landed_cost',
Cell: CheckBoxFieldCell,
disableSortBy: true,
disableResizing: true,
width: 70,
className: 'landed_cost',
},
...(landedCost
? [
{
Header: LandedCostHeaderCell,
accessor: 'landed_cost',
Cell: CheckBoxFieldCell,
disableSortBy: true,
disableResizing: true,
width: 100,
className: 'landed-cost',
},
]
: []),
{
Header: '',
accessor: 'action',

View File

@@ -1,7 +1,11 @@
import { AppToaster } from 'components';
import moment from 'moment';
import intl from 'react-intl-universal';
import { transformToForm, repeatValue } from 'utils';
import {
defaultFastFieldShouldUpdate,
transformToForm,
repeatValue,
} from 'utils';
const ERROR = {
EXPENSE_ALREADY_PUBLISHED: 'EXPENSE.ALREADY.PUBLISHED',
@@ -27,7 +31,7 @@ export const defaultExpenseEntry = {
amount: '',
expense_account_id: '',
description: '',
landed_cost: 0,
landed_cost: false,
};
export const defaultExpense = {
@@ -62,3 +66,23 @@ export const transformToEditForm = (
],
};
};
/**
* Detarmine cusotmers fast-field should update.
*/
export const customersFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.customers !== oldProps.customers ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmine accounts fast-field should update.
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { FastField, Field, ErrorMessage } from 'formik';
import { useFormikContext, FastField, Field, ErrorMessage } from 'formik';
import {
FormGroup,
Classes,
@@ -23,12 +23,21 @@ import withSettings from 'containers/Settings/withSettings';
import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes';
import { compose, inputIntent } from 'utils';
import {
sellDescriptionFieldShouldUpdate,
sellAccountFieldShouldUpdate,
sellPriceFieldShouldUpdate,
costPriceFieldShouldUpdate,
costAccountFieldShouldUpdate,
purchaseDescFieldShouldUpdate,
} from './utils';
/**
* Item form body.
*/
function ItemFormBody({ baseCurrency }) {
const { accounts } = useItemFormContext();
const { values } = useFormikContext();
return (
<div class="page-form__section page-form__section--selling-cost">
@@ -53,7 +62,11 @@ function ItemFormBody({ baseCurrency }) {
</FastField>
{/*------------- Selling price ------------- */}
<FastField name={'sell_price'}>
<FastField
name={'sell_price'}
sellable={values.sellable}
shouldUpdate={sellPriceFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'selling_price'} />}
@@ -78,7 +91,12 @@ function ItemFormBody({ baseCurrency }) {
</FastField>
{/*------------- Selling account ------------- */}
<FastField name={'sell_account_id'}>
<FastField
name={'sell_account_id'}
sellable={values.sellable}
accounts={accounts}
shouldUpdate={sellAccountFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'account'} />}
@@ -107,7 +125,11 @@ function ItemFormBody({ baseCurrency }) {
)}
</FastField>
<FastField name={'sell_description'}>
<FastField
name={'sell_description'}
sellable={values.sellable}
shouldUpdate={sellDescriptionFieldShouldUpdate}
>
{({ form: { values }, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
@@ -146,7 +168,11 @@ function ItemFormBody({ baseCurrency }) {
</FastField>
{/*------------- Cost price ------------- */}
<FastField name={'cost_price'}>
<FastField
name={'cost_price'}
purchasable={values.purchasable}
shouldUpdate={costPriceFieldShouldUpdate}
>
{({ field, form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'cost_price'} />}
@@ -171,7 +197,12 @@ function ItemFormBody({ baseCurrency }) {
</FastField>
{/*------------- Cost account ------------- */}
<FastField name={'cost_account_id'}>
<FastField
name={'cost_account_id'}
purchasable={values.purchasable}
accounts={accounts}
shouldUpdate={costAccountFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'account'} />}
@@ -200,7 +231,11 @@ function ItemFormBody({ baseCurrency }) {
)}
</FastField>
<FastField name={'purchase_description'}>
<FastField
name={'purchase_description'}
purchasable={values.purchasable}
shouldUpdate={purchaseDescFieldShouldUpdate}
>
{({ form: { values }, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}

View File

@@ -8,6 +8,7 @@ import classNames from 'classnames';
import withSettings from 'containers/Settings/withSettings';
import { accountsFieldShouldUpdate } from './utils';
import { compose, inputIntent } from 'utils';
import { ACCOUNT_TYPE } from 'common/accountTypes';
import { useItemFormContext } from './ItemFormProvider';
@@ -27,7 +28,11 @@ function ItemFormInventorySection({ baseCurrency }) {
<Row>
<Col xs={6}>
{/*------------- Inventory account ------------- */}
<FastField name={'inventory_account_id'}>
<FastField
name={'inventory_account_id'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { touched, error } }) => (
<FormGroup
label={<T id={'inventory_account'} />}

View File

@@ -21,6 +21,7 @@ import { CLASSES } from 'common/classes';
import { useItemFormContext } from './ItemFormProvider';
import { handleStringChange, inputIntent } from 'utils';
import { categoriesFieldShouldUpdate } from './utils';
/**
* Item form primary section.
@@ -130,7 +131,11 @@ export default function ItemFormPrimarySection() {
</FastField>
{/*----------- Item category ----------*/}
<FastField name={'category_id'}>
<FastField
name={'category_id'}
categories={itemsCategories}
shouldUpdate={categoriesFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'category'} />}

View File

@@ -1,6 +1,7 @@
import intl from 'react-intl-universal';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { defaultFastFieldShouldUpdate } from 'utils';
export const transitionItemTypeKeyToLabel = (itemTypeKey) => {
const table = {
@@ -28,7 +29,9 @@ export const handleDeleteErrors = (errors) => {
)
) {
AppToaster.show({
message: intl.get('you_could_not_delete_item_that_has_associated_inventory_adjustments_transacions'),
message: intl.get(
'you_could_not_delete_item_that_has_associated_inventory_adjustments_transacions',
),
intent: Intent.DANGER,
});
}
@@ -38,8 +41,83 @@ export const handleDeleteErrors = (errors) => {
)
) {
AppToaster.show({
message: intl.get('cannot_change_item_type_to_inventory_with_item_has_associated_transactions'),
message: intl.get(
'cannot_change_item_type_to_inventory_with_item_has_associated_transactions',
),
intent: Intent.DANGER,
});
}
};
/**
* Detarmines accounts fast field should update.
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines categories fast field should update.
*/
export const categoriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.categories !== oldProps.categories ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Sell price fast field should update.
*/
export const sellPriceFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.sellable !== oldProps.sellable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Sell account fast field should update.
*/
export const sellAccountFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.sellable !== oldProps.sellable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Sell description fast field should update.
*/
export const sellDescriptionFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.sellable !== oldProps.sellable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
export const costAccountFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.purchasable !== oldProps.purchasable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
export const costPriceFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.purchasable !== oldProps.purchasable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
export const purchaseDescFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.purchasable !== oldProps.purchasable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -48,7 +48,7 @@ function BillForm({
currency_code: baseCurrency,
}),
}),
[bill],
[bill, baseCurrency],
);
// Transform response error to fields.

View File

@@ -7,6 +7,7 @@ import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { ContactSelecetList, FieldRequiredHint, Icon } from 'components';
import { vendorsFieldShouldUpdate } from './utils';
import { useBillFormContext } from './BillFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -28,7 +29,11 @@ function BillFormHeader() {
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ------- Vendor name ------ */}
<FastField name={'vendor_id'}>
<FastField
name={'vendor_id'}
vendors={vendors}
shouldUpdate={vendorsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'vendor_name'} />}

View File

@@ -4,13 +4,23 @@ import { FastField } from 'formik';
import { CLASSES } from 'common/classes';
import { useBillFormContext } from './BillFormProvider';
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
import {
entriesFieldShouldUpdate
} from './utils';
/**
* Bill form body.
*/
export default function BillFormBody({ defaultBill }) {
const { items } = useBillFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<FastField name={'entries'}>
<FastField
name={'entries'}
items={items}
shouldUpdate={entriesFieldShouldUpdate}
>
{({
form: { values, setFieldValue },
field: { value },
@@ -25,6 +35,7 @@ export default function BillFormBody({ defaultBill }) {
errors={error}
linesNumber={4}
currencyCode={values.currency_code}
landedCost={true}
/>
)}
</FastField>

View File

@@ -2,7 +2,11 @@ import moment from 'moment';
import intl from 'react-intl-universal';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { transformToForm, repeatValue } from 'utils';
import {
defaultFastFieldShouldUpdate,
transformToForm,
repeatValue,
} from 'utils';
export const MIN_LINES_NUMBER = 4;
@@ -13,6 +17,7 @@ export const defaultBillEntry = {
discount: '',
quantity: '',
description: '',
landed_cost: false,
};
export const defaultBill = {
@@ -62,3 +67,23 @@ export const handleDeleteErrors = (errors) => {
});
}
};
/**
* Detarmines vendors fast field should update
*/
export const vendorsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.vendors !== oldProps.vendors ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines entries fast field should update.
*/
export const entriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -59,7 +59,7 @@ export function ActionsMenu({
/>
</If>
<MenuItem
// icon={<Icon icon="quick-payment-16" iconSize={16} />}
icon={<Icon icon="receipt-24" iconSize={16} />}
text={intl.get('allocate_landed_coast')}
onClick={safeCallback(onAllocateLandedCost, original)}
/>

View File

@@ -36,6 +36,7 @@ import {
fullAmountPaymentEntries,
amountPaymentEntries,
} from 'utils';
import { accountsFieldShouldUpdate, vendorsFieldShouldUpdate } from './utils';
/**
* Payment made form header fields.
@@ -48,17 +49,14 @@ function PaymentMadeFormHeaderFields({ baseCurrency }) {
} = useFormikContext();
// Payment made form context.
const {
vendors,
accounts,
isNewMode,
setPaymentVendorId,
} = usePaymentMadeFormContext();
const { vendors, accounts, isNewMode, setPaymentVendorId } =
usePaymentMadeFormContext();
// Sumation of payable full-amount.
const payableFullAmount = useMemo(() => safeSumBy(entries, 'due_amount'), [
entries,
]);
const payableFullAmount = useMemo(
() => safeSumBy(entries, 'due_amount'),
[entries],
);
// Handle receive full-amount click.
const handleReceiveFullAmountClick = () => {
@@ -78,7 +76,11 @@ function PaymentMadeFormHeaderFields({ baseCurrency }) {
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ------------ Vendor name ------------ */}
<FastField name={'vendor_id'}>
<FastField
name={'vendor_id'}
vendors={vendors}
shouldUpdate={vendorsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'vendor_name'} />}
@@ -157,7 +159,7 @@ function PaymentMadeFormHeaderFields({ baseCurrency }) {
small={true}
minimal={true}
>
<T id={'receive_full_amount'} /> (
<T id={'receive_full_amount'} /> (
<Money amount={payableFullAmount} currency={baseCurrency} />)
</Button>
</FormGroup>
@@ -184,7 +186,11 @@ function PaymentMadeFormHeaderFields({ baseCurrency }) {
</FastField>
{/* ------------ Payment account ------------ */}
<FastField name={'payment_account_id'}>
<FastField
name={'payment_account_id'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'payment_account'} />}

View File

@@ -1,5 +1,9 @@
import moment from 'moment';
import { safeSumBy, transformToForm } from 'utils';
import {
defaultFastFieldShouldUpdate,
safeSumBy,
transformToForm,
} from 'utils';
export const ERRORS = {
PAYMENT_NUMBER_NOT_UNIQUE: 'PAYMENT.NUMBER.NOT.UNIQUE',
@@ -9,10 +13,10 @@ export const ERRORS = {
export const defaultPaymentMadeEntry = {
bill_id: '',
payment_amount: '',
currency_code:'',
currency_code: '',
id: null,
due_amount: null,
amount:''
amount: '',
};
// Default initial values of payment made.
@@ -48,7 +52,26 @@ export const transformToNewPageEntries = (entries) => {
return entries.map((entry) => ({
...transformToForm(entry, defaultPaymentMadeEntry),
payment_amount: '',
currency_code:entry.currency_code,
currency_code: entry.currency_code,
}));
}
};
/**
* Detarmines vendors fast field when update.
*/
export const vendorsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.vendors !== oldProps.vendors ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines accounts fast field when update.
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -27,6 +27,7 @@ function EstimateFormHeader({
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
<EstimateFormHeaderFields />
<PageFormBigNumber
label={intl.get('amount')}
amount={totalDueAmount}

View File

@@ -15,6 +15,7 @@ import {
inputIntent,
handleDateChange,
} from 'utils';
import { customersFieldShouldUpdate } from './utils';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import {
@@ -67,7 +68,11 @@ function EstimateFormHeader({
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Customer name ----------- */}
<FastField name={'customer_id'}>
<FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer_name'} />}
@@ -170,7 +175,9 @@ function EstimateFormHeader({
}}
tooltip={true}
tooltipProps={{
content: <T id={'setting_your_auto_generated_estimate_number'}/>,
content: (
<T id={'setting_your_auto_generated_estimate_number'} />
),
position: Position.BOTTOM_LEFT,
}}
/>

View File

@@ -4,6 +4,7 @@ import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
import { useEstimateFormContext } from './EstimateFormProvider';
import { entriesFieldShouldUpdate } from './utils';
/**
* Estimate form items entries editor.
@@ -13,7 +14,11 @@ export default function EstimateFormItemsEntriesField() {
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<FastField name={'entries'}>
<FastField
name={'entries'}
items={items}
shouldUpdate={entriesFieldShouldUpdate}
>
{({
form: { values, setFieldValue },
field: { value },

View File

@@ -1,7 +1,12 @@
import React from 'react';
import { useFormikContext } from 'formik';
import moment from 'moment';
import { transactionNumber, repeatValue, transformToForm } from 'utils';
import {
defaultFastFieldShouldUpdate,
transactionNumber,
repeatValue,
transformToForm,
} from 'utils';
export const MIN_LINES_NUMBER = 4;
@@ -49,4 +54,24 @@ export const useObserveEstimateNoSettings = (prefix, nextNumber) => {
const estimateNo = transactionNumber(prefix, nextNumber);
setFieldValue('estimate_number', estimateNo);
}, [setFieldValue, prefix, nextNumber]);
}
};
/**
* Detarmines customers fast field when update.
*/
export const customersFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.customers !== oldProps.customers ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines entries fast field should update.
*/
export const entriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -10,7 +10,10 @@ import { FastField, Field, ErrorMessage } from 'formik';
import { FormattedMessage as T } from 'components';
import { momentFormatter, compose, tansformDateValue } from 'utils';
import classNames from 'classnames';
import { useObserveInvoiceNoSettings } from './utils';
import {
useObserveInvoiceNoSettings,
customerNameFieldShouldUpdate,
} from './utils';
import { CLASSES } from 'common/classes';
import {
ContactSelecetList,
@@ -58,15 +61,16 @@ function InvoiceFormHeaderFields({
};
// Syncs invoice number settings with form.
useObserveInvoiceNoSettings(
invoiceNumberPrefix,
invoiceNextNumber,
);
useObserveInvoiceNoSettings(invoiceNumberPrefix, invoiceNextNumber);
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Customer name ----------- */}
<FastField name={'customer_id'}>
<FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customerNameFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer_name'} />}
@@ -168,7 +172,9 @@ function InvoiceFormHeaderFields({
}}
tooltip={true}
tooltipProps={{
content: <T id={'setting_your_auto_generated_invoice_number'}/>,
content: (
<T id={'setting_your_auto_generated_invoice_number'} />
),
position: Position.BOTTOM_LEFT,
}}
/>

View File

@@ -4,6 +4,7 @@ import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
import { useInvoiceFormContext } from './InvoiceFormProvider';
import { entriesFieldShouldUpdate } from './utils';
/**
* Invoice items entries editor field.
@@ -13,7 +14,11 @@ export default function InvoiceItemsEntriesEditorField() {
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<FastField name={'entries'}>
<FastField
name={'entries'}
items={items}
shouldUpdate={entriesFieldShouldUpdate}
>
{({
form: { values, setFieldValue },
field: { value },

View File

@@ -11,7 +11,7 @@ import { updateItemsEntriesTotal } from 'containers/Entries/utils';
import { useFormikContext } from 'formik';
import { Intent } from '@blueprintjs/core';
import { orderingLinesIndexes } from 'utils';
import { defaultFastFieldShouldUpdate } from 'utils';
import intl from 'react-intl-universal';
import { ERROR } from 'common/errors';
import { AppToaster } from 'components';
@@ -100,4 +100,18 @@ export const useObserveInvoiceNoSettings = (prefix, nextNumber) => {
const invoiceNo = transactionNumber(prefix, nextNumber);
setFieldValue('invoice_no', invoiceNo);
}, [setFieldValue, prefix, nextNumber]);
};
};
export const customerNameFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.customers !== oldProps.customers ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
export const entriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -34,6 +34,7 @@ import {
} from 'components';
import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider';
import { ACCOUNT_TYPE } from 'common/accountTypes';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withSettings from 'containers/Settings/withSettings';
@@ -41,6 +42,8 @@ import {
useObservePaymentNoSettings,
amountPaymentEntries,
fullAmountPaymentEntries,
customersFieldShouldUpdate,
accountsFieldShouldUpdate,
} from './utils';
import { toSafeInteger } from 'lodash';
@@ -115,7 +118,11 @@ function PaymentReceiveHeaderFields({
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ------------- Customer name ------------- */}
<FastField name={'customer_id'}>
<FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer_name'} />}
@@ -247,7 +254,11 @@ function PaymentReceiveHeaderFields({
</FastField>
{/* ------------ Deposit account ------------ */}
<FastField name={'deposit_account_id'}>
<FastField
name={'deposit_account_id'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'deposit_to'} />}

View File

@@ -1,7 +1,12 @@
import React from 'react';
import { useFormikContext } from 'formik';
import moment from 'moment';
import { transactionNumber, transformToForm, safeSumBy } from 'utils';
import {
defaultFastFieldShouldUpdate,
transactionNumber,
transformToForm,
safeSumBy,
} from 'utils';
// Default payment receive entry.
export const defaultPaymentReceiveEntry = {
@@ -99,3 +104,23 @@ export const useObservePaymentNoSettings = (prefix, nextNumber) => {
setFieldValue('payment_receive_no', invoiceNo);
}, [setFieldValue, prefix, nextNumber]);
};
/**
* Detarmines the customers fast-field should update.
*/
export const customersFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.customers !== oldProps.customers ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines the accounts fast-field should update.
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -28,7 +28,11 @@ import {
inputIntent,
} from 'utils';
import { useReceiptFormContext } from './ReceiptFormProvider';
import { useObserveReceiptNoSettings } from './utils';
import {
accountsFieldShouldUpdate,
customersFieldShouldUpdate,
useObserveReceiptNoSettings,
} from './utils';
/**
* Receipt form header fields.
@@ -70,7 +74,11 @@ function ReceiptFormHeader({
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Customer name ----------- */}
<FastField name={'customer_id'}>
<FastField
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer_name'} />}
@@ -94,7 +102,11 @@ function ReceiptFormHeader({
</FastField>
{/* ----------- Deposit account ----------- */}
<FastField name={'deposit_account_id'}>
<FastField
name={'deposit_account_id'}
accounts={accounts}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'deposit_account'} />}

View File

@@ -4,13 +4,14 @@ import { FastField } from 'formik';
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
import { CLASSES } from 'common/classes';
import { useReceiptFormContext } from './ReceiptFormProvider';
import { entriesFieldShouldUpdate } from './utils';
export default function ReceiptItemsEntriesEditor({ defaultReceipt }) {
const { items } = useReceiptFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<FastField name={'entries'}>
<FastField name={'entries'} items={items} shouldUpdate={entriesFieldShouldUpdate}>
{({
form: { values, setFieldValue },
field: { value },

View File

@@ -1,7 +1,12 @@
import React from 'react';
import { useFormikContext } from 'formik';
import moment from 'moment';
import { transactionNumber, repeatValue, transformToForm } from 'utils';
import {
defaultFastFieldShouldUpdate,
transactionNumber,
repeatValue,
transformToForm,
} from 'utils';
export const MIN_LINES_NUMBER = 4;
@@ -42,7 +47,6 @@ export const transformToEditForm = (receipt) => ({
],
});
export const useObserveReceiptNoSettings = (prefix, nextNumber) => {
const { setFieldValue } = useFormikContext();
@@ -50,4 +54,34 @@ export const useObserveReceiptNoSettings = (prefix, nextNumber) => {
const receiptNo = transactionNumber(prefix, nextNumber);
setFieldValue('receipt_number', receiptNo);
}, [setFieldValue, prefix, nextNumber]);
}
};
/**
* Detarmines entries fast field should update.
*/
export const entriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines accounts fast field should update.
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
/**
* Detarmines customers fast field should update.
*/
export const customersFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.customers !== oldProps.customers ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -1146,5 +1146,11 @@
"No items": "No items",
"cannot_delete_bill_that_has_associated_landed_cost_transactions": "Cannot delete bill that has associated landed cost transactions.",
"couldn_t_delete_expense_transaction_has_associated_located_landed_cost_transaction": "Couldn't delete expense transaction has associated located landed cost transaction",
"the_landed_cost_has_been_created_successfully": "The landed cost has been created successfully"
"the_landed_cost_has_been_created_successfully": "The landed cost has been created successfully",
"Select transaction": "Select transaction",
"Select transaction entry": "Select transaction entry",
"From transaction": "From transaction",
"Landed": "Landed",
"This options allows you to be able to add additional cost eg. freight then allocate cost to the items in your bills.": "This options allows you to be able to add additional cost eg. freight then allocate cost to the items in your bills.",
"Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?": "Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?"
}

View File

@@ -10,7 +10,7 @@
.th,
.td {
border-left: 1px dashed #e2e2e2;
border-left: 1px solid #e2e2e2;
&.index {
text-align: center;
@@ -55,6 +55,19 @@
margin-bottom: auto;
}
}
&.landed-cost{
.bp3-control{
margin-top: 0;
margin-left: 34px;
}
.bp3-control-indicator{
height: 18px;
width: 18px;
border-color: #e0e0e0;
}
}
}
.tr {
.bp3-form-group:not(.bp3-intent-danger) .bp3-input,

View File

@@ -0,0 +1,21 @@
.details-menu {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
&.is-vertical {}
.detail-item {
&__label {
color: #666666;
font-weight: 500;
}
&__content {
text-transform: capitalize;
margin: 5px 0;
}
}
}

View File

@@ -0,0 +1,17 @@
.bp3-drawer {
.bp3-drawer-header {
margin-bottom: 2px;
background-color: #FFF;
.bp3-heading {
font-weight: 500;
}
.bp3-heading,
.bp3-icon {
color: #354152;
}
}
}

View File

@@ -1,14 +1,4 @@
.bp3-drawer-header {
box-shadow: 0 0 0;
.bp3-heading{
font-size: 16px;
}
.bp3-button{
min-height: 28px;
min-width: 28px;
}
}
.account-drawer {
background-color: #fbfbfb;
@@ -94,29 +84,4 @@
}
}
}
}
.bp3-drawer.bp3-position-right {
bottom: 0;
right: 0;
top: 0;
overflow: auto;
height: 100%;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.bp3-drawer-header {
margin-bottom: 2px;
box-shadow: (0, 0, 0);
background-color: #6a7993;
.bp3-heading,
.bp3-icon {
color: white;
}
}
}

View File

@@ -4,14 +4,15 @@
.bp3-tabs {
.bp3-tab-list {
position: relative;
background-color: #FFF;
&:before {
content: '';
position: absolute;
bottom: 0;
width: 100%;
height: 2px;
background: #f0f0f0;
background: #e1e2e8;
}
> *:not(:last-child) {
@@ -29,14 +30,18 @@
}
}
}
.bp3-tab-panel{
margin-top: 0;
.card{
margin: 15px;
}
}
}
.bigcapital-datatable {
.datatable--landed-cost-transactions {
.table {
max-height: 500px;
border: 1px solid #d1dee2;
min-width: auto;
margin: 12px;
.tbody,
.tbody-inner {
@@ -48,34 +53,13 @@
}
.tbody {
.tr .td {
padding: 0.8rem;
padding: 0.6rem;
&.amount{
font-weight: 600;
}
}
}
}
}
}
.bp3-drawer.bp3-position-right {
bottom: 0;
right: 0;
top: 0;
overflow: auto;
height: 100%;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.bp3-drawer-header {
margin-bottom: 2px;
box-shadow: (0, 0, 0);
background-color: #6a7993;
.bp3-heading,
.bp3-icon {
color: white;
}
}
}
}

View File

@@ -122,6 +122,7 @@
top: 0;
overflow: auto;
height: 100%;
.bp3-drawer-header .bp3-heading {
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -1,6 +1,5 @@
.journal-drawer,
.expense-drawer {
background: #f5f5f5;
&__content {
display: flex;
@@ -18,8 +17,8 @@
justify-content: flex-start;
margin: 15px 0 20px;
font-size: 14px;
// color: #333333;
color: #666666;
> div {
flex-grow: 1;
span {
@@ -44,17 +43,17 @@
&--table {
flex-grow: 1;
flex-shrink: 0;
.table {
color: #666666;
font-size: 14px;
.thead .tr .th .resizer {
display: none;
}
.thead .th {
background: transparent;
color: #222222;
border-bottom: 1px solid #000000;
padding: 0.5rem;
}
.thead .th,
.tbody .tr .td {
background: transparent;
padding: 0.8rem 0.5rem;
@@ -63,7 +62,6 @@
.desc {
margin: 20px 0 60px;
// margin: 20px 0;
> b {
color: #2f2f2f;
}
@@ -93,25 +91,3 @@
}
}
}
.bp3-drawer.bp3-position-right {
bottom: 0;
right: 0;
top: 0;
overflow: auto;
height: 100%;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.bp3-drawer-header {
margin-bottom: 2px;
box-shadow: (0, 0, 0);
background-color: #6a7993;
.bp3-heading,
.bp3-icon {
color: white;
}
}
}

View File

@@ -11,7 +11,6 @@ body.page-bill-edit{
padding-bottom: 64px;
}
.page-form--bill{
$self: '.page-form';
@@ -36,7 +35,7 @@ body.page-bill-edit{
max-width: 440px;
}
&.form-group{
&.form-group{
&--expiration-date{
max-width: 340px;

View File

@@ -1,11 +1,10 @@
.dashboard__insider--expenses {
.dashboard__insider--expenses{
.bigcapital-datatable {
.bigcapital-datatable{
.tbody{
.tr .td.total_amount{
span{
.tbody {
.tr .td.total_amount {
span {
font-weight: 600;
}
}
@@ -13,36 +12,64 @@
}
}
.page-form--expense{
.page-form--expense {
$self: '.page-form';
#{$self}__header{
#{$self}__header {
display: flex;
&-fields{
&-fields {
flex: 1 0 0;
}
.bp3-label{
.bp3-label {
min-width: 140px;
}
.bp3-form-content{
.bp3-form-content {
width: 100%;
}
.bp3-form-group{
.bp3-form-group {
margin-bottom: 18px;
&.bp3-inline{
max-width: 440px;
&.bp3-inline {
max-width: 440px;
}
}
}
.form-group--description{
.datatable-editor--expense-form {
.table {
.tbody {
.tr .td {
&.landed-cost {
.bp3-control {
margin-top: 0;
margin-left: 34px;
}
.bp3-control-indicator {
height: 18px;
width: 18px;
border-color: #e0e0e0;
}
}
}
}
}
}
.form-group--description {
max-width: 500px;
textarea{
textarea {
min-height: 60px;
width: 100%;
}

View File

@@ -1,3 +1,7 @@
// Noto Sans
// -------------------------------------
@font-face {
font-family: Noto Sans;
src: local('Noto Sans'), url('../fonts/NotoSans-SemiBold.woff') format('woff');
@@ -30,46 +34,8 @@
font-display: swap;
}
// arabic regular
@font-face {
font-family: Noto Sans Arabic;
src: local('Noto Sans'),
url('../fonts/NotoSansArabicUI-SemiCondensed.woff') format('woff');
font-style: normal;
font-weight: 400;
font-display: swap;
}
// arabic black
@font-face {
font-family: Noto Sans Arabic;
src: local('Noto Sans'),
url('../fonts/NotoSansArabicUI-SemiCondensedBlack.woff') format('woff');
font-style: normal;
font-weight: 900;
font-display: swap;
}
//arabic Medium
@font-face {
font-family: Noto Sans Arabic;
src: local('Noto Sans'),
url('../fonts/NotoSansArabicUI-SemiCondensedMedium.woff') format('woff');
font-style: normal;
font-weight: 500;
font-display: swap;
}
//arabic SemiBold
@font-face {
font-family: Noto Sans Arabic;
src: local('Noto Sans'),
url('../fonts/NotoSansArabicUI-SemiCondensedSemiBold.woff') format('woff');
font-style: normal;
font-weight: 600;
font-display: swap;
}
// Segoe UI Arabic
// -------------------------------------
// Segoe UI Arabic - Regular
@font-face {
font-family: 'Segoe UI';

View File

@@ -16,7 +16,7 @@ $menu-item-color-active: $light-gray3;
$breadcrumbs-collapsed-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#6B8193' enable-background='new 0 0 16 16' xml:space='preserve'><g><circle cx='2' cy='8.03' r='2'/><circle cx='14' cy='8.03' r='2'/><circle cx='8' cy='8.03' r='2'/></g></svg>");
$sidebar-zindex: 15;
$pt-font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont,
$pt-font-family: 'Noto Sans', -apple-system, BlinkMacSystemFont,
Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue,
Icons16, sans-serif;

View File

@@ -90,7 +90,9 @@ export const objectKeysTransform = (obj, transform) => {
export const compose = (...funcs) =>
funcs.reduce(
(a, b) => (...args) => a(b(...args)),
(a, b) =>
(...args) =>
a(b(...args)),
(arg) => arg,
);
@@ -639,7 +641,32 @@ const getCurrenciesOptions = () => {
currency_code: currencyCode,
formatted_name: `${currencyCode} - ${currency.name}`,
};
})
});
};
export const currenciesOptions = getCurrenciesOptions();
/**
* Deeply get a value from an object via its path.
*/
function getIn(obj, key, def, p = 0) {
const path = _.toPath(key);
while (obj && p < path.length) {
obj = obj[path[p++]];
}
return obj === undefined ? def : obj;
}
export const currenciesOptions = getCurrenciesOptions();
export const defaultFastFieldShouldUpdate = (props, prevProps) => {
return (
props.name !== prevProps.name ||
getIn(props.formik.values, prevProps.name) !==
getIn(prevProps.formik.values, prevProps.name) ||
getIn(props.formik.errors, prevProps.name) !==
getIn(prevProps.formik.errors, prevProps.name) ||
getIn(props.formik.touched, prevProps.name) !==
getIn(prevProps.formik.touched, prevProps.name) ||
Object.keys(prevProps).length !== Object.keys(props).length ||
props.formik.isSubmitting !== prevProps.formik.isSubmitting
);
};