diff --git a/src/components/Dialog/DialogFooter.js b/src/components/Dialog/DialogFooter.js
new file mode 100644
index 000000000..604015683
--- /dev/null
+++ b/src/components/Dialog/DialogFooter.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import styled from 'styled-components';
+import { Classes } from '@blueprintjs/core';
+
+export function DialogFooter({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+const DialogFooterRoot = styled.div`
+ display: flex;
+`;
diff --git a/src/components/Dialog/DialogFooterActions.js b/src/components/Dialog/DialogFooterActions.js
index 91c856e9a..e24461203 100644
--- a/src/components/Dialog/DialogFooterActions.js
+++ b/src/components/Dialog/DialogFooterActions.js
@@ -14,13 +14,11 @@ export function DialogFooterActions({ alignment = 'right', children }) {
}
const DialogFooterActionsRoot = styled.div`
- margin-left: -10px;
- margin-right: -10px;
- justify-content: ${(props) =>
- props.alignment === 'right' ? 'flex-end' : 'flex-start'};
+ ${(props) =>
+ props.alignment === 'right' ? 'margin-left: auto;' : 'margin-right: auto;'};
.bp3-button {
- margin-left: 10px;
- margin-left: 10px;
+ margin-left: 5px;
+ margin-left: 5px;
}
`;
diff --git a/src/components/Dialog/index.js b/src/components/Dialog/index.js
index c7c0982fb..58890b836 100644
--- a/src/components/Dialog/index.js
+++ b/src/components/Dialog/index.js
@@ -3,4 +3,5 @@
export * from './Dialog';
export * from './DialogFooterActions';
export * from './DialogSuspense';
-export * from './DialogContent';
\ No newline at end of file
+export * from './DialogContent';
+export * from './DialogFooter';
\ No newline at end of file
diff --git a/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostDialogProvider.js b/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostDialogProvider.js
index 780ea0621..adf48ca5b 100644
--- a/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostDialogProvider.js
+++ b/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostDialogProvider.js
@@ -1,6 +1,16 @@
import React from 'react';
+import { defaultTo, get } from 'lodash';
import { DialogContent } from 'components';
-import { useBill, useCreateLandedCost } from 'hooks/query';
+import {
+ useBill,
+ useCreateLandedCost,
+ useLandedCostTransaction,
+} from 'hooks/query';
+import {
+ getEntriesByTransactionId,
+ getCostTransactionById,
+ getTransactionEntryById,
+} from './utils';
const AllocateLandedCostDialogContext = React.createContext();
@@ -13,22 +23,79 @@ function AllocateLandedCostDialogProvider({
dialogName,
...props
}) {
+ const [transactionsType, setTransactionsType] = React.useState(null);
+ const [transactionId, setTransactionId] = React.useState(null);
+ const [transactionEntryId, setTransactionEntryId] = React.useState(null);
+
// Handle fetch bill details.
const { isLoading: isBillLoading, data: bill } = useBill(billId, {
enabled: !!billId,
});
-
+ // Retrieve the landed cost transactions based on the given transactions type.
+ const {
+ data: { transactions: landedCostTransactions },
+ } = useLandedCostTransaction(transactionsType, {
+ enabled: !!transactionsType,
+ });
+ // Landed cost selected transaction.
+ const costTransaction = React.useMemo(
+ () =>
+ transactionId
+ ? getCostTransactionById(transactionId, landedCostTransactions)
+ : null,
+ [transactionId, landedCostTransactions],
+ );
+ // Retrieve the cost transaction entry.
+ const costTransactionEntry = React.useMemo(
+ () =>
+ costTransaction && transactionEntryId
+ ? getTransactionEntryById(costTransaction, transactionEntryId)
+ : null,
+ [costTransaction, transactionEntryId],
+ );
+ // Retrieve entries of the given transaction id.
+ const costTransactionEntries = React.useMemo(
+ () =>
+ transactionId
+ ? getEntriesByTransactionId(landedCostTransactions, transactionId)
+ : [],
+ [landedCostTransactions, transactionId],
+ );
// Create landed cost mutations.
const { mutateAsync: createLandedCostMutate } = useCreateLandedCost();
- // provider payload.
+ // Retrieve the unallocate cost amount of cost transaction.
+ const unallocatedCostAmount = defaultTo(
+ get(costTransactionEntry, 'unallocated_cost_amount'),
+ 0,
+ );
+
+ // Retrieve the unallocate cost amount of cost transaction.
+ const formattedUnallocatedCostAmount = defaultTo(
+ get(costTransactionEntry, 'formatted_unallocated_cost_amount'),
+ 0,
+ );
+
+ // Provider payload.
const provider = {
isBillLoading,
bill,
dialogName,
query,
createLandedCostMutate,
+ costTransaction,
+ costTransactionEntries,
+ transactionsType,
+ landedCostTransactions,
+ setTransactionsType,
+ setTransactionId,
+ setTransactionEntryId,
+ costTransactionEntry,
+ transactionEntryId,
+ transactionId,
billId,
+ unallocatedCostAmount,
+ formattedUnallocatedCostAmount,
};
return (
diff --git a/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFloatingActions.js b/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFloatingActions.js
index 2b4cded08..de5b7e16e 100644
--- a/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFloatingActions.js
+++ b/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFloatingActions.js
@@ -1,8 +1,13 @@
import React from 'react';
-import { Intent, Button, Classes } from '@blueprintjs/core';
-import { FormattedMessage as T } from 'components';
-
+import { Intent, Button } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
+import styled from 'styled-components';
+import {
+ DialogFooter,
+ DialogFooterActions,
+ FormattedMessage as T,
+} from 'components';
+
import { useAllocateLandedConstDialogContext } from './AllocateLandedCostDialogProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
@@ -13,7 +18,8 @@ function AllocateLandedCostFloatingActions({
}) {
// Formik context.
const { isSubmitting } = useFormikContext();
- const { dialogName } = useAllocateLandedConstDialogContext();
+ const { dialogName, costTransactionEntry, formattedUnallocatedCostAmount } =
+ useAllocateLandedConstDialogContext();
// Handle cancel button click.
const handleCancelBtnClick = (event) => {
@@ -21,22 +27,41 @@ function AllocateLandedCostFloatingActions({
};
return (
-
-
+
+
+ {costTransactionEntry && (
+
+ Unallocated cost Amount:{' '}
+ {formattedUnallocatedCostAmount}
+
+ )}
+
+
+
-
-
+
+
);
}
export default compose(withDialogActions)(AllocateLandedCostFloatingActions);
+
+const UnallocatedAmount = styled.div`
+ color: #3f5278;
+ align-self: center;
+
+ strong {
+ color: #353535;
+ padding-left: 4px;
+ }
+`;
diff --git a/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostForm.js b/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostForm.js
index 3b6ded70a..5cb1a2fa2 100644
--- a/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostForm.js
+++ b/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostForm.js
@@ -2,8 +2,6 @@ import React from 'react';
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';
@@ -14,20 +12,19 @@ import AllocateLandedCostFormContent from './AllocateLandedCostFormContent';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose, transformToForm } from 'utils';
+const defaultInitialItem = {
+ entry_id: '',
+ cost: '',
+};
+
// Default form initial values.
const defaultInitialValues = {
transaction_type: 'Bill',
- transaction_date: moment(new Date()).format('YYYY-MM-DD'),
transaction_id: '',
transaction_entry_id: '',
amount: '',
allocation_method: 'quantity',
- items: [
- {
- entry_id: '',
- cost: '',
- },
- ],
+ items: [defaultInitialItem],
};
/**
@@ -37,8 +34,13 @@ function AllocateLandedCostForm({
// #withDialogActions
closeDialog,
}) {
- const { dialogName, bill, billId, createLandedCostMutate } =
- useAllocateLandedConstDialogContext();
+ const {
+ dialogName,
+ bill,
+ billId,
+ createLandedCostMutate,
+ unallocatedCostAmount,
+ } = useAllocateLandedConstDialogContext();
// Initial form values.
const initialValues = {
@@ -49,11 +51,10 @@ function AllocateLandedCostForm({
cost: '',
})),
};
- const amount = sumBy(initialValues.items, 'amount');
// Handle form submit.
const handleFormSubmit = (values, { setSubmitting }) => {
- setSubmitting(false);
+ setSubmitting(true);
// Filters the entries has no cost.
const entries = values.items
@@ -81,13 +82,16 @@ function AllocateLandedCostForm({
// Handle the request error.
const onError = () => {
setSubmitting(false);
- AppToaster.show({ message: 'Something went wrong!', intent: Intent.DANGER });
+ AppToaster.show({
+ message: 'Something went wrong!',
+ intent: Intent.DANGER,
+ });
};
createLandedCostMutate([billId, form]).then(onSuccess).catch(onError);
};
// Computed validation schema.
- const validationSchema = AllocateLandedCostFormSchema(amount);
+ const validationSchema = AllocateLandedCostFormSchema(unallocatedCostAmount);
return (
+export const AllocateLandedCostFormSchema = (maxAmount) =>
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(),
+ transaction_type: Yup.string()
+ .required()
+ .label(intl.get('transaction_type')),
+ transaction_id: Yup.string()
+ .required()
+ .label(intl.get('transaction_number')),
+ transaction_entry_id: Yup.string()
+ .required()
+ .label(intl.get('transaction_line')),
+ amount: Yup.number().max(maxAmount).label(intl.get('amount')),
+ allocation_method: Yup.string().required().trim(),
items: Yup.array().of(
Yup.object().shape({
entry_id: Yup.number().nullable(),
diff --git a/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFormContent.js b/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFormContent.js
index e1f5f08c0..51c22052a 100644
--- a/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFormContent.js
+++ b/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFormContent.js
@@ -1,16 +1,37 @@
import React from 'react';
-import { Form } from 'formik';
+import { Form, useFormikContext } from 'formik';
+import { FormObserver } from 'components';
import AllocateLandedCostFormFields from './AllocateLandedCostFormFields';
+import { useAllocateLandedConstDialogContext } from './AllocateLandedCostDialogProvider';
import AllocateLandedCostFloatingActions from './AllocateLandedCostFloatingActions';
/**
* Allocate landed cost form content.
*/
export default function AllocateLandedCostFormContent() {
+ const { values } = useFormikContext();
+
+ // Allocate landed cost dialog context.
+ const { setTransactionsType, setTransactionId, setTransactionEntryId } =
+ useAllocateLandedConstDialogContext();
+
+ // Handle the form change.
+ const handleFormChange = (values) => {
+ if (values.transaction_type) {
+ setTransactionsType(values.transaction_type);
+ }
+ if (values.transaction_id) {
+ setTransactionId(values.transaction_id);
+ }
+ if (values.transaction_entry_id) {
+ setTransactionEntryId(values.transaction_entry_id);
+ }
+ };
return (
);
}
diff --git a/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFormFields.js b/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFormFields.js
index 4861a65eb..d71ebab2c 100644
--- a/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFormFields.js
+++ b/src/containers/Dialogs/AllocateLandedCostDialog/AllocateLandedCostFormFields.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
+import { FastField, Field, ErrorMessage } from 'formik';
import {
Classes,
FormGroup,
@@ -14,31 +14,31 @@ import { inputIntent, handleStringChange } from 'utils';
import { FieldRequiredHint, ListSelect } from 'components';
import { CLASSES } from 'common/classes';
import allocateLandedCostType from 'common/allocateLandedCostType';
-import { useLandedCostTransaction } from 'hooks/query';
import AllocateLandedCostFormBody from './AllocateLandedCostFormBody';
-import { getEntriesByTransactionId, allocateCostToEntries } from './utils';
+import {
+ transactionsSelectShouldUpdate,
+ allocateCostToEntries,
+ resetAllocatedCostEntries,
+} from './utils';
+import { useAllocateLandedConstDialogContext } from './AllocateLandedCostDialogProvider';
/**
* Allocate landed cost form fields.
*/
export default function AllocateLandedCostFormFields() {
- const { values } = useFormikContext();
-
- const {
- data: { transactions },
- } = useLandedCostTransaction(values.transaction_type);
-
- // Retrieve entries of the given transaction id.
- const transactionEntries = React.useMemo(
- () => getEntriesByTransactionId(transactions, values.transaction_id),
- [transactions, values.transaction_id],
- );
+ // Allocated landed cost dialog.
+ const { costTransactionEntries, landedCostTransactions } =
+ useAllocateLandedConstDialogContext();
return (
{/*------------Transaction type -----------*/}
-
+
{({
form: { values, setFieldValue },
field: { value },
@@ -55,9 +55,14 @@ export default function AllocateLandedCostFormFields() {
{
+ const { items } = values;
+
setFieldValue('transaction_type', type.value);
setFieldValue('transaction_id', '');
setFieldValue('transaction_entry_id', '');
+
+ setFieldValue('amount', '');
+ setFieldValue('items', resetAllocatedCostEntries(items));
}}
filterable={false}
selectedItem={value}
@@ -70,7 +75,11 @@ export default function AllocateLandedCostFormFields() {
{/*------------ Transaction -----------*/}
-
+
{({ form, field: { value }, meta: { error, touched } }) => (
}
@@ -81,10 +90,14 @@ export default function AllocateLandedCostFormFields() {
inline={true}
>
{
+ const { items } = form.values;
form.setFieldValue('transaction_id', id);
form.setFieldValue('transaction_entry_id', '');
+
+ form.setFieldValue('amount', '');
+ form.setFieldValue('items', resetAllocatedCostEntries(items));
}}
filterable={false}
selectedItem={value}
@@ -99,8 +112,12 @@ export default function AllocateLandedCostFormFields() {
{/*------------ Transaction line -----------*/}
- 0}>
-
+ 0}>
+
{({ form, field: { value }, meta: { error, touched } }) => (
}
@@ -113,16 +130,20 @@ export default function AllocateLandedCostFormFields() {
inline={true}
>
{
+ items={costTransactionEntries}
+ onItemSelect={({ id, unallocated_cost_amount }) => {
const { items, allocation_method } = form.values;
- form.setFieldValue('amount', amount);
form.setFieldValue('transaction_entry_id', id);
+ form.setFieldValue('amount', unallocated_cost_amount);
form.setFieldValue(
'items',
- allocateCostToEntries(amount, allocation_method, items),
+ allocateCostToEntries(
+ unallocated_cost_amount,
+ allocation_method,
+ items,
+ ),
);
}}
filterable={false}
@@ -177,12 +198,12 @@ export default function AllocateLandedCostFormFields() {
>
{
- const { amount, items, allocation_method } = form.values;
+ const { amount, items } = form.values;
form.setFieldValue('allocation_method', _value);
form.setFieldValue(
'items',
- allocateCostToEntries(amount, allocation_method, items),
+ allocateCostToEntries(amount, _value, items),
);
})}
selectedValue={value}
diff --git a/src/containers/Dialogs/AllocateLandedCostDialog/utils.js b/src/containers/Dialogs/AllocateLandedCostDialog/utils.js
index 21d5fa076..a19fff723 100644
--- a/src/containers/Dialogs/AllocateLandedCostDialog/utils.js
+++ b/src/containers/Dialogs/AllocateLandedCostDialog/utils.js
@@ -1,5 +1,14 @@
import { sumBy, round } from 'lodash';
import * as R from 'ramda';
+import { defaultFastFieldShouldUpdate } from 'utils';
+
+/**
+ * Retrieve the landed cost transaction by the given id.
+ */
+export function getCostTransactionById(id, transactions) {
+ return transactions.find((trans) => trans.id === id);
+}
+
/**
* Retrieve transaction entries of the given transaction id.
*/
@@ -8,6 +17,10 @@ export function getEntriesByTransactionId(transactions, id) {
return transaction ? transaction.entries : [];
}
+export function getTransactionEntryById(transaction, transactionEntryId) {
+ return transaction.entries.find((entry) => entry.id === transactionEntryId);
+}
+
export function allocateCostToEntries(total, allocateType, entries) {
return R.compose(
R.when(
@@ -60,3 +73,18 @@ export function allocateCostByQuantity(total, entries) {
cost: round(entry.percentageOfQuantity * total, 2),
}));
}
+
+/**
+ * Detarmines the transactions selet field when should update.
+ */
+export function transactionsSelectShouldUpdate(newProps, oldProps) {
+ return (
+ newProps.transactions !== oldProps.transactions ||
+ defaultFastFieldShouldUpdate(newProps, oldProps)
+ );
+}
+
+
+export function resetAllocatedCostEntries(entries) {
+ return entries.map((entry) => ({ ...entry, cost: 0 }));
+}
\ No newline at end of file
diff --git a/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormFloatingActions.js b/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormFloatingActions.js
index e823fba9d..c579f0767 100644
--- a/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormFloatingActions.js
+++ b/src/containers/Dialogs/SMSMessageDialog/SMSMessageFormFloatingActions.js
@@ -1,9 +1,12 @@
import React from 'react';
-import { Intent, Button, Classes } from '@blueprintjs/core';
+import { Intent, Button } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
-import { DialogFooterActions, FormattedMessage as T } from 'components';
-
+import {
+ DialogFooter,
+ DialogFooterActions,
+ FormattedMessage as T,
+} from 'components';
import { useSMSMessageDialogContext } from './SMSMessageDialogProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -28,7 +31,7 @@ function SMSMessageFormFloatingActions({
};
return (
-
+
-
+
);
}
diff --git a/src/containers/NotifyViaSMS/NotifyViaSMSFormFloatingActions.js b/src/containers/NotifyViaSMS/NotifyViaSMSFormFloatingActions.js
index dbe701aaf..e79e69b21 100644
--- a/src/containers/NotifyViaSMS/NotifyViaSMSFormFloatingActions.js
+++ b/src/containers/NotifyViaSMS/NotifyViaSMSFormFloatingActions.js
@@ -1,8 +1,12 @@
import React from 'react';
import { useFormikContext } from 'formik';
-import { Intent, Button, Classes } from '@blueprintjs/core';
+import { Intent, Button } from '@blueprintjs/core';
-import { DialogFooterActions, FormattedMessage as T } from 'components';
+import {
+ DialogFooter,
+ DialogFooterActions,
+ FormattedMessage as T,
+} from 'components';
/**
*
@@ -17,7 +21,7 @@ export default function NotifyViaSMSFormFloatingActions({ onCancel }) {
};
return (
-
+
-
+
);
}
diff --git a/src/hooks/query/bills.js b/src/hooks/query/bills.js
index d46225079..2ee4b366e 100644
--- a/src/hooks/query/bills.js
+++ b/src/hooks/query/bills.js
@@ -22,6 +22,10 @@ const commonInvalidateQueries = (queryClient) => {
// Invalidate financial reports.
queryClient.invalidateQueries(t.FINANCIAL_REPORT);
+
+ // Invalidate landed cost.
+ queryClient.invalidateQueries(t.LANDED_COST);
+ queryClient.invalidateQueries(t.LANDED_COST_TRANSACTION);
};
/**
diff --git a/src/hooks/query/expenses.js b/src/hooks/query/expenses.js
index df5000d13..152af27f2 100644
--- a/src/hooks/query/expenses.js
+++ b/src/hooks/query/expenses.js
@@ -25,6 +25,10 @@ const commonInvalidateQueries = (queryClient) => {
// Invalidate the cashflow transactions.
queryClient.invalidateQueries(t.CASH_FLOW_TRANSACTIONS);
queryClient.invalidateQueries(t.CASHFLOW_ACCOUNT_TRANSACTIONS_INFINITY);
+
+ // Invalidate landed cost.
+ queryClient.invalidateQueries(t.LANDED_COST);
+ queryClient.invalidateQueries(t.LANDED_COST_TRANSACTION);
};
const transformExpenses = (response) => ({
diff --git a/src/hooks/query/landedCost.js b/src/hooks/query/landedCost.js
index 6249a6061..57dbcfe17 100644
--- a/src/hooks/query/landedCost.js
+++ b/src/hooks/query/landedCost.js
@@ -39,6 +39,7 @@ export function useCreateLandedCost(props) {
export function useDeleteLandedCost(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
+
return useMutation(
(landedCostId) =>
apiRequest.delete(`purchases/landed-cost/${landedCostId}`),
@@ -65,7 +66,6 @@ export function useLandedCostTransaction(query, props) {
},
{
select: (res) => res.data,
-
defaultData: {
transactions: [],
},