WIP / Feature & Fix Expense /Customer

This commit is contained in:
elforjani3
2020-06-24 00:28:15 +02:00
parent c9cf54cbf9
commit aac138aa18
29 changed files with 762 additions and 440 deletions

View File

@@ -1,43 +1,49 @@
import React, {useCallback, useState} from 'react';
import {
MenuItem,
Button,
} from '@blueprintjs/core';
import {Select} from '@blueprintjs/select';
import React, { useCallback, useState } from 'react';
import { MenuItem, Button } from '@blueprintjs/core';
import { Select } from '@blueprintjs/select';
export default function AccountsSelectList({
accounts,
onAccountSelected,
error,
error = [],
initialAccount,
defautlSelectText = 'Select account'
defautlSelectText = 'Select account',
}) {
const [selectedAccount, setSelectedAccount] = useState(
initialAccount || null
initialAccount || null,
);
// Account item of select accounts field.
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
return (
<MenuItem text={item.name} label={item.code} key={item.id} onClick={handleClick} />
<MenuItem
text={item.name}
label={item.code}
key={item.id}
onClick={handleClick}
/>
);
}, []);
const onAccountSelect = useCallback((account) => {
setSelectedAccount({ ...account });
onAccountSelected && onAccountSelected(account);
}, [setSelectedAccount, onAccountSelected]);
const onAccountSelect = useCallback(
(account) => {
setSelectedAccount({ ...account });
onAccountSelected && onAccountSelected(account);
},
[setSelectedAccount, onAccountSelected],
);
return (
<Select
items={accounts}
noResults={<MenuItem disabled={true} text='No results.' />}
noResults={<MenuItem disabled={true} text="No results." />}
itemRenderer={accountItem}
popoverProps={{ minimal: true }}
filterable={true}
onItemSelect={onAccountSelect}>
onItemSelect={onAccountSelect}
>
<Button
text={selectedAccount ? selectedAccount.name : defautlSelectText}
/>
</Select>
);
}
}

View File

@@ -1,11 +1,7 @@
import React, {useCallback, useMemo} from 'react';
import React, { useCallback, useMemo } from 'react';
import AccountsSelectList from 'components/AccountsSelectList';
import classNames from 'classnames';
import {
FormGroup,
Classes,
Intent,
} from '@blueprintjs/core';
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
// Account cell renderer.
const AccountCellRenderer = ({
@@ -14,31 +10,38 @@ const AccountCellRenderer = ({
cell: { value: initialValue },
payload: { accounts, updateData, errors },
}) => {
const handleAccountSelected = useCallback((account) => {
updateData(index, id, account.id);
}, [updateData, index, id]);
const handleAccountSelected = useCallback(
(account) => {
updateData(index, id, account.id);
},
[updateData, index, id],
);
const { account_id = false } = (errors[index] || {});
const { account_id = false, expense_account_id = false } =
errors[index] || {};
const initialAccount = useMemo(() =>
accounts.find(a => a.id === initialValue),
[accounts, initialValue]);
const initialAccount = useMemo(
() => accounts.find((a) => a.id === initialValue),
[accounts, initialValue],
);
return (
<FormGroup
intent={account_id ? Intent.DANGER : ''}
intent={account_id || expense_account_id ? Intent.DANGER : ''}
className={classNames(
'form-group--select-list',
'form-group--account',
Classes.FILL)}
>
Classes.FILL,
)}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={handleAccountSelected}
error={account_id}
initialAccount={initialAccount} />
error={[account_id, expense_account_id]}
initialAccount={initialAccount}
/>
</FormGroup>
);
};
export default AccountCellRenderer;
export default AccountCellRenderer;

View File

@@ -9,8 +9,7 @@ import withCustomersActions from 'containers/Customers/withCustomersActions';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
import {compose} from 'utils';
import { compose } from 'utils';
function MakeJournalEntriesPage({
// #withCustomersActions
@@ -25,20 +24,25 @@ function MakeJournalEntriesPage({
const history = useHistory();
const { id } = useParams();
const fetchAccounts = useQuery('accounts-list',
(key) => requestFetchAccounts());
const fetchAccounts = useQuery('accounts-list', (key) =>
requestFetchAccounts(),
);
const fetchCustomers = useQuery('customers-list',
(key) => requestFetchCustomers());
const fetchCustomers = useQuery('customers-list', (key) =>
requestFetchCustomers(),
);
const fetchJournal = useQuery(
id && ['manual-journal', id],
(key, journalId) => requestFetchManualJournal(journalId));
(key, journalId) => requestFetchManualJournal(journalId),
);
const handleFormSubmit = useCallback((payload) => {
payload.redirect &&
history.push('/manual-journals');
}, [history]);
const handleFormSubmit = useCallback(
(payload) => {
payload.redirect && history.push('/manual-journals');
},
[history],
);
const handleCancel = useCallback(() => {
history.push('/manual-journals');
@@ -51,11 +55,13 @@ function MakeJournalEntriesPage({
fetchAccounts.isFetching ||
fetchCustomers.isFetching
}
name={'make-journal-page'}>
name={'make-journal-page'}
>
<MakeJournalEntriesForm
onFormSubmit={handleFormSubmit}
manualJournalId={id}
onCancelForm={handleCancel} />
onCancelForm={handleCancel}
/>
</DashboardInsider>
);
}
@@ -64,4 +70,4 @@ export default compose(
withAccountsActions,
withCustomersActions,
withManualJournalsActions,
)(MakeJournalEntriesPage);
)(MakeJournalEntriesPage);

View File

@@ -25,15 +25,31 @@ function Customer({
requestFetchCustomers({}),
);
const fetchCustomerDatails =useQuery(id && ['customer-detail',id],()=>requestFetchCustomers())
const fetchCustomerDatails = useQuery(id && ['customer-detail', id], () =>
requestFetchCustomers(),
);
const handleFormSubmit = useCallback(
(payload) => {
payload.redirect && history.push('/customers');
},
[history],
);
const handleCancel = useCallback(() => {
history.goBack();
}, [history]);
return (
<DashboardInsider
// formik={formik}
loading={ fetchCustomerDatails.isFetching || fetchCustomers.isFetching}
loading={fetchCustomerDatails.isFetching || fetchCustomers.isFetching}
name={'customer-form'}
>
<CustomerForm customerId={id} />
<CustomerForm
onFormSubmit={handleFormSubmit}
customerId={id}
onCancelForm={handleCancel}
/>
</DashboardInsider>
);
}

View File

@@ -0,0 +1,57 @@
import React from 'react';
import { Intent, Button } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
export default function CustomerFloatingFooter({
formik: { isSubmitting, resetForm },
onSubmitClick,
onCancelClick,
customer,
}) {
return (
<div className={'form__floating-footer'}>
<Button
intent={Intent.PRIMARY}
disabled={isSubmitting}
type="submit"
onClick={() => {
onSubmitClick({ publish: true, redirect: true });
}}
>
{customer && customer.id ? <T id={'edit'} /> : <T id={'save'} />}
</Button>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
className={'ml1'}
name={'save_and_new'}
onClick={() => {
onSubmitClick({ publish: true, redirect: false });
}}
>
<T id={'save_new'} />
</Button>
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={() => {
onSubmitClick({ publish: false, redirect: false });
}}
>
<T id={'save_as_draft'} />
</Button>
<Button
className={'ml1'}
onClick={() => {
onCancelClick && onCancelClick();
}}
>
<T id={'close'} />
</Button>
</div>
);
}

View File

@@ -31,6 +31,7 @@ import withCustomers from 'containers/Customers//withCustomers';
import useMedia from 'hooks/useMedia';
import { compose } from 'utils';
import CustomerFloatingFooter from './CustomerFooter';
function CustomerForm({
// #withDashboardActions
@@ -40,7 +41,7 @@ function CustomerForm({
customers,
//#withCustomerDetail
customerDetail,
customer,
//#withCustomersActions
requestSubmitCustomer,
@@ -50,9 +51,13 @@ function CustomerForm({
// #withMediaActions
requestSubmitMedia,
requestDeleteMedia,
//#Props
onFormSubmit,
onCancelForm,
}) {
const { formatMessage } = useIntl();
const history = useHistory();
const [payload, setPayload] = useState({});
const {
setFiles,
@@ -127,22 +132,30 @@ function CustomerForm({
const initialValues = useMemo(
() => ({
...(customerDetail
...(customer
? {
...pick(customerDetail, Object.keys(defaultInitialValues)),
...pick(customer, Object.keys(defaultInitialValues)),
}
: {
...defaultInitialValues,
}),
}),
[customerDetail, defaultInitialValues],
[customer, defaultInitialValues],
);
const saveInvokeSubmit = useCallback(
(payload) => {
onFormSubmit && onFormSubmit(payload);
},
[onFormSubmit],
);
useEffect(() => {
customerDetail && customerDetail.id
customer && customer.id
? changePageTitle(formatMessage({ id: 'edit_customer_details' }))
: changePageTitle(formatMessage({ id: 'new_customer' }));
}, [changePageTitle, customerDetail, formatMessage]);
}, [changePageTitle, customer, formatMessage]);
const formik = useFormik({
enableReinitialize: true,
@@ -152,9 +165,9 @@ function CustomerForm({
},
onSubmit: (values, { setSubmitting, resetForm, setErrors }) => {
const formValues = { ...values };
if (customerDetail && customerDetail.id) {
requestEditCustomer(customerDetail.id, formValues)
const formValues = { ...values, status: payload.publish };
if (customer && customer.id) {
requestEditCustomer(customer.id, formValues)
.then((response) => {
AppToaster.show({
message: formatMessage({
@@ -163,8 +176,9 @@ function CustomerForm({
intent: Intent.SUCCESS,
});
setSubmitting(false);
history.push('/customers');
// history.push('/customers');
resetForm();
saveInvokeSubmit({ action: 'update', ...payload });
})
.catch((errors) => {
setSubmitting(false);
@@ -178,7 +192,9 @@ function CustomerForm({
}),
intent: Intent.SUCCESS,
});
history.push('/customers');
// history.push('/customers');
setSubmitting(false);
saveInvokeSubmit({ action: 'new', ...payload });
})
.catch((errors) => {
setSubmitting(false);
@@ -202,8 +218,8 @@ function CustomerForm({
);
const initialAttachmentFiles = useMemo(() => {
return customerDetail && customerDetail.media
? customerDetail.media.map((attach) => ({
return customer && customer.media
? customer.media.map((attach) => ({
preview: attach.attachment_file,
upload: true,
metadata: { ...attach },
@@ -224,11 +240,20 @@ function CustomerForm({
},
[setDeletedFiles, deletedFiles],
);
const handleSubmitClick = useCallback(
(payload) => {
setPayload(payload);
formik.handleSubmit();
},
[setPayload, formik],
);
const handleCancelClickBtn = () => {
history.goBack();
};
const handleCancelClick = useCallback(
(payload) => {
onCancelForm && onCancelForm(payload);
},
[onCancelForm],
);
return (
<div className={'customer-form'}>
<form onSubmit={formik.handleSubmit}>
@@ -396,34 +421,14 @@ function CustomerForm({
</FormGroup>
<CustomersTabs formik={formik} />
<div class="form__floating-footer">
<Button intent={Intent.PRIMARY} disabled={isSubmitting} type="submit">
{customerDetail && customerDetail.id ? (
<T id={'edit'} />
) : (
<T id={'save'} />
)}
</Button>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
className={'ml1'}
name={'save_and_new'}
>
<T id={'save_new'} />
</Button>
<Button className={'ml1'} disabled={isSubmitting}>
<T id={'save_as_draft'} />
</Button>
<Button className={'ml1'} onClick={handleCancelClickBtn}>
<T id={'close'} />
</Button>
</div>
</form>
<CustomerFloatingFooter
formik={formik}
onSubmitClick={handleSubmitClick}
customer={customer}
onCancelClick={handleCancelClick}
/>
</div>
);
}

View File

@@ -137,21 +137,21 @@ function CustomersList({
[fetchCustomers],
);
// Handle items bulk delete button click.,
// Handle Customers bulk delete button click.,
const handleBulkDelete = useCallback(
(itemsIds) => {
setBulkDelete(itemsIds);
(customersIds) => {
setBulkDelete(customersIds);
},
[setBulkDelete],
);
// Handle cancel accounts bulk delete.
// Handle cancel cusomters bulk delete.
const handleCancelBulkDelete = useCallback(() => {
setBulkDelete(false);
}, []);
// Handle confirm items bulk delete.
// Handle confirm customers bulk delete.
const handleConfirmBulkDelete = useCallback(() => {
requestDeleteBulkCustomers(bulkDelete)
.then(() => {
@@ -163,7 +163,7 @@ function CustomersList({
intent: Intent.SUCCESS,
});
})
.catch((errors) => {
.catch((error) => {
setBulkDelete(false);
});
}, [requestDeleteBulkCustomers, bulkDelete, formatMessage]);

View File

@@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import { getCustomerById } from 'store/customers/customers.reducer';
const mapStateToProps = (state, props) => ({
customerDetail: getCustomerById(state, props.customerId),
customer: getCustomerById(state, props.customerId),
});
export default connect(mapStateToProps);

View File

@@ -4,13 +4,14 @@ import {
submitCustomer,
editCustomer,
deleteCustomer,
deleteBulkCustomers
} from 'store/customers/customers.actions';
import t from 'store/types';
export const mapDispatchToProps = (dispatch) => ({
requestFetchCustomers: (query) => dispatch(fetchCustomers({ query })),
requestDeleteCustomer: (id) => dispatch(deleteCustomer({ id })),
// requestDeleteBulkCustomers:(ids)=>dispatch(deleteBulkCustomers({ids})),
requestDeleteBulkCustomers:(ids)=>dispatch(deleteBulkCustomers({ids})),
requestSubmitCustomer: (form) => dispatch(submitCustomer({ form })),
requestEditCustomer: (id, form) => dispatch(editCustomer({ id, form })),

View File

@@ -33,15 +33,15 @@ function ExpenseDataTable({
//#withExpenes
expenses,
expensesLoading,
// #withDashboardActions
changeCurrentView,
changePageSubtitle,
setTopbarEditView,
// #withView
viewMeta,
// #ownProps
loading,
onFetchData,
@@ -98,10 +98,9 @@ function ExpenseDataTable({
const actionMenuList = useCallback(
(expense) => (
<Menu>
<MenuItem
text={formatMessage({ id: 'view_details' })} />
<MenuItem text={formatMessage({ id: 'view_details' })} />
<MenuDivider />
<If condition={expenses.published}>
<If condition={!expense.published}>
<MenuItem
text={formatMessage({ id: 'publish_expense' })}
onClick={handlePublishExpense(expense)}
@@ -119,23 +118,35 @@ function ExpenseDataTable({
/>
</Menu>
),
[handleEditExpense, handleDeleteExpense, handlePublishExpense, formatMessage],
[
handleEditExpense,
handleDeleteExpense,
handlePublishExpense,
formatMessage,
],
);
const onRowContextMenu = useCallback((cell) => {
return actionMenuList(cell.row.original);
}, [actionMenuList]);
const onRowContextMenu = useCallback(
(cell) => {
return actionMenuList(cell.row.original);
},
[actionMenuList],
);
const expenseAccountAccessor = (expense) => {
if (expense.categories.length === 1) {
return expense.categories[0].expense_account.name;
} else if (expense.categories.length > 1) {
const mutliCategories = expense.categories.map(category =>
(<div>- {category.expense_account.name} ${ category.amount }</div>)
const expenseAccountAccessor = (_expense) => {
if (_expense.categories.length === 1) {
return _expense.categories[0].expense_account.name;
} else if (_expense.categories.length > 1) {
const mutliCategories = _expense.categories.map((category) => (
<div>
- {category.expense_account.name} ${category.amount}
</div>
));
return (
<Tooltip content={mutliCategories}>{'- Multi Categories -'}</Tooltip>
);
return <Tooltip content={mutliCategories}>{ '- Multi Categories -' }</Tooltip>;
}
}
};
const columns = useMemo(
() => [
@@ -179,7 +190,7 @@ function ExpenseDataTable({
id: 'publish',
Header: formatMessage({ id: 'publish' }),
accessor: (r) => {
return !r.published ? (
return r.published ? (
<Tag minimal={true}>
<T id={'published'} />
</Tag>

View File

@@ -2,42 +2,56 @@ import React from 'react';
import { Intent, Button } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
export default function ExpenseFooter({
export default function ExpenseFloatingFooter({
formik: { isSubmitting },
onSubmitClick,
onCancelClick,
expense,
}) {
return (
<div className={'form__floating-footer'}>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
className={'ml1'}
name={'save'}
disabled={isSubmitting}
type="submit"
onClick={() => {
onSubmitClick({ publish: true, redirect: true });
}}
>
<T id={'save'} />
{expense && expense.id ? <T id={'edit'} /> : <T id={'save'} />}
</Button>
<Button
disabled={isSubmitting}
className={'button-secondary ml1'}
intent={Intent.PRIMARY}
className={'ml1'}
name={'save_and_new'}
onClick={() => {
onSubmitClick({ publish: true, redirect: false });
}}
>
<T id={'save_as_draft'} />
<T id={'save_new'} />
</Button>
<Button
className={'button-secondary ml1'}
className={'ml1'}
disabled={isSubmitting}
onClick={() => {
onCancelClick && onCancelClick({ publish: false, redirect: false });
onSubmitClick({ publish: false, redirect: false });
}}
>
<T id={'cancel'} />
<T id={'save_as_draft'} />
</Button>
<Button
className={'ml1'}
onClick={() => {
onCancelClick && onCancelClick();
}}
>
<T id={'close'} />
</Button>
</div>
);
};
}

View File

@@ -11,7 +11,7 @@ import moment from 'moment';
import { Intent, FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { pick } from 'lodash';
import { useQuery } from 'react-query';
import { useQuery, queryCache } from 'react-query';
import { Col, Row } from 'react-grid-system';
import ExpenseFormHeader from './ExpenseFormHeader';
@@ -45,12 +45,14 @@ function ExpenseForm({
//#withExpenseDetail
// @todo expenseDetail to expense
expenseDetail,
expense,
// #own Props
expenseId,
onFormSubmit,
onCancelForm,
onClickAddNewRow,
onClickRemoveRow,
}) {
const { formatMessage } = useIntl();
const [payload, setPayload] = useState({});
@@ -75,14 +77,14 @@ function ExpenseForm({
};
useEffect(() => {
if (expenseDetail && expenseDetail.id) {
if (expense && expense.id) {
changePageTitle(formatMessage({ id: 'edit_expense' }));
// changePageSubtitle(`No. ${expenseDetail.payment_account_id}`);
} else {
changePageTitle(formatMessage({ id: 'new_expense' }));
}
// @todo not functions just states.
}, [changePageTitle, changePageSubtitle, expenseDetail, formatMessage]);
}, [changePageTitle, changePageSubtitle, expense, formatMessage]);
const validationSchema = Yup.object().shape({
beneficiary: Yup.string()
@@ -106,7 +108,13 @@ function ExpenseForm({
index: Yup.number().nullable(),
amount: Yup.number().nullable(),
// @todo expense_account_id is required.
expense_account_id: Yup.number().nullable(),
// expense_account_id: Yup.number().nullable(),
expense_account_id: Yup.number()
.nullable()
.when(['amount'], {
is: (amount) => amount,
then: Yup.number().required(),
}),
description: Yup.string().nullable(),
}),
),
@@ -116,6 +124,7 @@ function ExpenseForm({
(payload) => {
onFormSubmit && onFormSubmit(payload);
},
[onFormSubmit],
);
@@ -147,32 +156,43 @@ function ExpenseForm({
[defaultCategory],
);
const orderingCategoriesIndex = (categories) => {
return categories.map((category, index) => ({
...category,
index: index + 1,
}));
};
const initialValues = useMemo(
() => ({
...(expenseDetail
...(expense
? {
...pick(expenseDetail, Object.keys(defaultInitialValues)),
categories: expenseDetail.categories.map((category) => ({
...pick(expense, Object.keys(defaultInitialValues)),
categories: expense.categories.map((category) => ({
...pick(category, Object.keys(defaultCategory)),
}),
),
})),
}
: {
...defaultInitialValues,
categories: orderingCategoriesIndex(
defaultInitialValues.categories,
),
}),
}),
[expenseDetail, defaultInitialValues, defaultCategory],
[expense, defaultInitialValues, defaultCategory],
);
console.log(initialValues.categories, 'ERR');
const initialAttachmentFiles = useMemo(() => {
return expenseDetail && expenseDetail.media
? expenseDetail.media.map((attach) => ({
return expense && expense.media
? expense.media.map((attach) => ({
preview: attach.attachment_file,
uploaded: true,
metadata: { ...attach },
}))
: [];
}, [expenseDetail]);
}, [expense]);
const formik = useFormik({
enableReinitialize: true,
@@ -182,11 +202,12 @@ function ExpenseForm({
},
onSubmit: async (values, { setSubmitting, setErrors, resetForm }) => {
const categories = values.categories.filter(
(category) => category.amount || category.index,
(category) =>
category.amount && category.index && category.expense_account_id,
);
const form = {
...values,
published: payload.publish,
publish: payload.publish,
categories,
};
@@ -194,8 +215,8 @@ function ExpenseForm({
new Promise((resolve, reject) => {
const requestForm = { ...form, media_ids: mdeiaIds };
if (expenseDetail && expenseDetail.id) {
requestEditExpense(expenseDetail.id, requestForm)
if (expense && expense.id) {
requestEditExpense(expense.id, requestForm)
.then((response) => {
AppToaster.show({
message: formatMessage(
@@ -208,10 +229,8 @@ function ExpenseForm({
saveInvokeSubmit({ action: 'update', ...payload });
clearSavedMediaIds([]);
resetForm();
resolve(response);
})
.catch((errors) => {
// @todo handle errors.
if (errors.find((e) => e.type === 'TOTAL.AMOUNT.EQUALS.ZERO')) {
}
setErrors(
@@ -235,10 +254,11 @@ function ExpenseForm({
intent: Intent.SUCCESS,
});
setSubmitting(false);
formik.resetForm();
saveInvokeSubmit({ action: 'new', ...payload });
clearSavedMediaIds();
resetForm();
resolve(response);
// resolve(response);
})
.catch((errors) => {
setSubmitting(false);
@@ -263,11 +283,14 @@ function ExpenseForm({
const handleSubmitClick = useCallback(
(payload) => {
setPayload(payload);
// formik.resetForm();
formik.handleSubmit();
},
[setPayload, formik],
);
console.log(formik.values, 'VALUES');
const handleCancelClick = useCallback(
(payload) => {
onCancelForm && onCancelForm(payload);
@@ -285,7 +308,7 @@ function ExpenseForm({
},
[setDeletedFiles, deletedFiles],
);
// @todo @mohamed
const fetchHook = useQuery('expense-form', () => requestFetchExpensesTable());
return (
@@ -318,13 +341,15 @@ function ExpenseForm({
hint={'Attachments: Maxiumum size: 20MB'}
/>
</div>
<ExpenseFloatingFooter
formik={formik}
onSubmitClick={handleSubmitClick}
onCancelClick={handleCancelClick}
/>
</form>
<ExpenseFloatingFooter
formik={formik}
onSubmitClick={handleSubmitClick}
expense={expense}
onCancelClick={handleCancelClick}
// onClickAddNewRow={}
// onClickRemoveRow={}
/>
</div>
);
}

View File

@@ -24,6 +24,7 @@ function ExpenseFormHeader({
formik: { errors, touched, setFieldValue, getFieldProps, values },
currenciesList,
accounts,
accountsTypes,
}) {
const [selectedItems, setSelectedItems] = useState({});
@@ -103,7 +104,6 @@ function ExpenseFormHeader({
const onItemsSelect = useCallback(
(filedName) => {
return (filed) => {
// @todo @mohamed
setSelectedItems({
...selectedItems,
[filedName]: filed,
@@ -234,10 +234,7 @@ function ExpenseFormHeader({
<Col width={200}>
<FormGroup
label={<T id={'ref_no'} />}
className={classNames(
'form-group--ref_no',
Classes.FILL,
)}
className={classNames('form-group--ref_no', Classes.FILL)}
intent={
errors.reference_no && touched.reference_no && Intent.DANGER
}
@@ -260,8 +257,9 @@ function ExpenseFormHeader({
}
export default compose(
withAccounts(({ accounts }) => ({
withAccounts(({ accounts, accountsTypes }) => ({
accounts,
accountsTypes,
})),
withCurrencies(({ currenciesList }) => ({
currenciesList,

View File

@@ -41,15 +41,20 @@ function ExpenseTable({
(rowIndex, columnId, value) => {
const newRows = rows.map((row, index) => {
if (index === rowIndex) {
return { ...rows[rowIndex], [columnId]: value };
return {
...rows[rowIndex],
[columnId]: value,
};
}
return { ...row };
});
setRow(newRows);
setFieldValue(
'categories',
newRows.map((row) => ({
newRows.map((row, index) => ({
...omit(row, ['rowType']),
index: index + 1,
})),
);
},
@@ -60,15 +65,28 @@ function ExpenseTable({
const handleRemoveRow = useCallback(
(rowIndex) => {
const removeIndex = parseInt(rowIndex, 10);
const newRows = rows.filter((row, index) => index !== removeIndex);
const newRows = rows.filter((row, index) => index !== removeIndex);
setRow([...newRows]);
setFieldValue(
'categories',
newRows
.filter((row) => row.rowType === 'editor')
.map((row) => ({ ...omit(row, ['rowType']) })),
.map((row, index) => ({
...omit(row, ['rowType']),
index: index + 1,
})),
);
// const newRows = rows.filter((row, index) => index !== removeIndex);
// setRow([...newRows]);
// setFieldValue(
// 'categories',
// newRows
// .filter((row) => row.rowType === 'editor')
// .map((row) => ({ ...omit(row, ['rowType']) })),
// );
onClickRemoveRow && onClickRemoveRow(removeIndex);
},
[rows, setFieldValue, onClickRemoveRow],
@@ -82,7 +100,7 @@ function ExpenseTable({
data,
payload,
}) => {
if (data.length <= index + 2) {
if (data.length <= index + 1) {
return '';
}
const onClickRemoveRole = () => {
@@ -104,7 +122,7 @@ function ExpenseTable({
// Total text cell renderer.
const TotalExpenseCellRenderer = (chainedComponent) => (props) => {
if (props.data.length === props.row.index + 2) {
if (props.data.length <= props.row.index + 1) {
return (
<span>
{formatMessage({ id: 'total_currency' }, { currency: 'USD' })}
@@ -115,14 +133,14 @@ function ExpenseTable({
};
const NoteCellRenderer = (chainedComponent) => (props) => {
if (props.data.length === props.row.index + 2) {
if (props.data.length === props.row.index + 1) {
return '';
}
return chainedComponent(props);
};
const TotalAmountCellRenderer = (chainedComponent, type) => (props) => {
if (props.data.length === props.row.index + 2) {
if (props.data.length === props.row.index + 1) {
const total = props.data.reduce((total, entry) => {
const amount = parseInt(entry[type], 10);
const computed = amount ? total + amount : total;
@@ -147,7 +165,12 @@ function ExpenseTable({
disableSortBy: true,
},
{
Header: (<>{ formatMessage({ id: 'expense_category' }) }<Hint /></>),
Header: (
<>
{formatMessage({ id: 'expense_category' })}
<Hint />
</>
),
id: 'expense_account_id',
accessor: 'expense_account_id',
Cell: TotalExpenseCellRenderer(AccountsListFieldCell),
@@ -193,7 +216,7 @@ function ExpenseTable({
const rowClassNames = useCallback(
(row) => ({
'row--total': rows.length === row.index + 2,
'row--total': rows.length === row.index + 1,
}),
[rows],
);

View File

@@ -14,6 +14,7 @@ import { compose } from 'utils';
function Expenses({
// #withwithAccountsActions
requestFetchAccounts,
requestFetchAccountTypes,
// #withExpensesActions
requestFetchExpense,
@@ -24,18 +25,15 @@ function Expenses({
const history = useHistory();
const { id } = useParams();
// @todo
const fetchAccounts = useQuery('accounts-expense-list', (key) =>
const fetchAccounts = useQuery('accounts-list', (key) =>
requestFetchAccounts(),
);
// @todo
const fetchExpense = useQuery(id && ['expense', id], (key, expense_Id) =>
requestFetchExpense(expense_Id),
);
// @todo
const fetchCurrencies = useQuery('currencies-expense-list', () =>
const fetchCurrencies = useQuery('currencies', () =>
requestFetchCurrencies(),
);
const handleFormSubmit = useCallback(
@@ -46,7 +44,7 @@ function Expenses({
);
const handleCancel = useCallback(() => {
history.push('/expenses-list');
history.goBack();
}, [history]);
return (
@@ -56,6 +54,7 @@ function Expenses({
fetchAccounts.isFetching ||
fetchCurrencies.isFetching
}
name={'expense-form'}
>
<ExpenseForm
onFormSubmit={handleFormSubmit}

View File

@@ -49,7 +49,7 @@ function ExpensesList({
});
const fetchExpenses = useQuery(
['expenses-table', expensesTableQuery],
['expense-form', expensesTableQuery],
() => requestFetchExpensesTable(),
);
@@ -210,7 +210,7 @@ function ExpensesList({
</p>
</Alert>
{/* <Alert
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={
<T id={'delete_count'} values={{ count: selectedRowsCount }} />
@@ -223,10 +223,10 @@ function ExpensesList({
>
<p>
<T
id={'once_delete_these_journalss_you_will_not_able_restore_them'}
id={'once_delete_these_expenses_you_will_not_able_restore_them'}
/>
</p>
</Alert> */}
</Alert>
</DashboardPageContent>
</DashboardInsider>
);

View File

@@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import { getExpenseById } from 'store/expenses/expenses.reducer';
const mapStateToProps = (state, props) => ({
expenseDetail: getExpenseById(state, props.expenseId),
expense: getExpenseById(state, props.expenseId),
});
export default connect(mapStateToProps);

View File

@@ -49,7 +49,7 @@ const ItemCategoryList = ({
}, [id, changePageTitle, formatMessage]);
const fetchCategories = useQuery(
['items-categories-table', filter],
['items-categories-list', filter],
(key, query) => requestFetchItemCategories(query),
);
@@ -183,7 +183,9 @@ const ItemCategoryList = ({
>
<p>
<FormattedHTMLMessage
id={'once_delete_these_item_categories_you_will_not_able_restore_them'}
id={
'once_delete_these_item_categories_you_will_not_able_restore_them'
}
/>
</p>
</Alert>

View File

@@ -1,4 +1,4 @@
import React, { useState, useMemo, useCallback,useEffect } from 'react';
import React, { useState, useMemo, useCallback, useEffect } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import {
@@ -26,16 +26,17 @@ import Dragzone from 'components/Dragzone';
import { ListSelect } from 'components';
import withItemsActions from 'containers/Items/withItemsActions';
import withItemCategories from 'containers/Items/withItemCategories'
import withItemCategories from 'containers/Items/withItemCategories';
import withAccounts from 'containers/Accounts/withAccounts';
import withMediaActions from 'containers/Media/withMediaActions';
import useMedia from 'hooks/useMedia';
import withItemDetail from 'containers/Items/withItemDetail'
import withItemDetail from 'containers/Items/withItemDetail';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withAccountDetail from 'containers/Accounts/withAccountDetail';
import { compose } from 'utils';
import { resolve } from 'p-progress';
import ItemFloatingFooter from './ItemsFooter';
const ItemForm = ({
// #withItemActions
@@ -45,7 +46,7 @@ const ItemForm = ({
accounts,
itemDetail,
onFormSubmit,
onCancelForm,
onCancelForm,
// #withDashboardActions
changePageTitle,
@@ -55,10 +56,10 @@ const ItemForm = ({
// #withMediaActions
requestSubmitMedia,
requestDeleteMedia,
requestDeleteMedia,
}) => {
const [payload, setPayload] = useState({});
const history = useHistory();
const { formatMessage } = useIntl();
const {
@@ -72,22 +73,34 @@ const ItemForm = ({
deleteCallback: requestDeleteMedia,
});
const ItemTypeDisplay = useMemo(() => [
{ value: null, label: formatMessage({id:'select_item_type'}) },
{ value: 'service', label: formatMessage({id:'service'}) },
{ value: 'inventory', label: formatMessage({id:'inventory'}) },
{ value: 'non-inventory', label: formatMessage({id:'non_inventory'}) },
], [formatMessage]);
const ItemTypeDisplay = useMemo(
() => [
{ value: null, label: formatMessage({ id: 'select_item_type' }) },
{ value: 'service', label: formatMessage({ id: 'service' }) },
{ value: 'inventory', label: formatMessage({ id: 'inventory' }) },
{ value: 'non-inventory', label: formatMessage({ id: 'non_inventory' }) },
],
[formatMessage],
);
const validationSchema = Yup.object().shape({
active: Yup.boolean(),
name: Yup.string().required().label(formatMessage({id:'item_name_'})),
type: Yup.string().trim().required().label(formatMessage({id:'item_type_'})),
name: Yup.string()
.required()
.label(formatMessage({ id: 'item_name_' })),
type: Yup.string()
.trim()
.required()
.label(formatMessage({ id: 'item_type_' })),
sku: Yup.string().trim(),
cost_price: Yup.number(),
sell_price: Yup.number(),
cost_account_id: Yup.number().required().label(formatMessage({id:'cost_account_id'})),
sell_account_id: Yup.number().required().label(formatMessage({id:'sell_account_id'})),
cost_account_id: Yup.number()
.required()
.label(formatMessage({ id: 'cost_account_id' })),
sell_account_id: Yup.number()
.required()
.label(formatMessage({ id: 'sell_account_id' })),
inventory_account_id: Yup.number().when('type', {
is: (value) => value === 'inventory',
then: Yup.number().required(),
@@ -111,26 +124,33 @@ const ItemForm = ({
category_id: null,
note: '',
}),
[]
[],
);
const initialValues = useMemo(
() => ({
...(itemDetail
? {
...pick(itemDetail, Object.keys(defaultInitialValues)),
}
: {
...defaultInitialValues,
}),
}),
[itemDetail, defaultInitialValues],
);
const initialValues = useMemo(() => ({
...(itemDetail) ? {
...pick(itemDetail, Object.keys(defaultInitialValues)),
} : {
...defaultInitialValues,
}
}), [itemDetail, defaultInitialValues]);
const saveInvokeSubmit = useCallback((payload) => {
onFormSubmit && onFormSubmit(payload)
}, [onFormSubmit]);
const saveInvokeSubmit = useCallback(
(payload) => {
onFormSubmit && onFormSubmit(payload);
},
[onFormSubmit],
);
useEffect(() => {
itemDetail && itemDetail.id ?
changePageTitle(formatMessage({id:'edit_item_details'})) :
changePageTitle(formatMessage({id:'new_item'}));
}, [changePageTitle,itemDetail,formatMessage]);
itemDetail && itemDetail.id
? changePageTitle(formatMessage({ id: 'edit_item_details' }))
: changePageTitle(formatMessage({ id: 'new_item' }));
}, [changePageTitle, itemDetail, formatMessage]);
const {
getFieldProps,
@@ -140,65 +160,95 @@ const ItemForm = ({
errors,
handleSubmit,
isSubmitting,
resetForm
} = useFormik({
enableReinitialize: true,
validationSchema: validationSchema,
initialValues: {
...initialValues,
},
onSubmit: (values, { setSubmitting,resetForm,setErrors }) => {
onSubmit: (values, { setSubmitting, resetForm, setErrors }) => {
const saveItem = (mediaIds) => {
const formValues = { ...values, media_ids: mediaIds };
if(itemDetail && itemDetail.id ){
requestEditItem(itemDetail.id,formValues)
.then((response)=>{
AppToaster.show({
message:formatMessage({
id:'the_item_has_been_successfully_edited',
},{
number:itemDetail.id
}),
intent:Intent.SUCCESS
});
setSubmitting(false);
saveInvokeSubmit({action:'update',...payload})
history.push('/items');
resetForm();
}).catch((errors)=>{
setSubmitting(false)
});
}else{
requestSubmitItem(formValues).then((response) => {
AppToaster.show({
message: formatMessage({
id: 'service_has_been_successful_created',
}, {
name: values.name,
service: formatMessage({ id: 'item' }),
}),
intent: Intent.SUCCESS,
});
queryCache.removeQueries(['items-table']);
history.push('/items');
});
const formValues = {
...values,
status: payload.publish,
media_ids: mediaIds,
};
if (itemDetail && itemDetail.id) {
requestEditItem(itemDetail.id, formValues)
.then((response) => {
AppToaster.show({
message: formatMessage(
{
id: 'the_item_has_been_successfully_edited',
},
{
number: itemDetail.id,
},
),
intent: Intent.SUCCESS,
});
setSubmitting(false);
saveInvokeSubmit({ action: 'update', ...payload });
// history.push('/items');
resetForm();
resolve(response);
})
.catch((errors) => {
setSubmitting(false);
});
} else {
requestSubmitItem(formValues)
.then((response) => {
AppToaster.show({
message: formatMessage(
{
id: 'service_has_been_successful_created',
},
{
name: values.name,
service: formatMessage({ id: 'item' }),
},
),
intent: Intent.SUCCESS,
});
setSubmitting(false);
resetForm();
saveInvokeSubmit({ action: 'new', ...payload });
queryCache.removeQueries(['items-table']);
// history.push('/items');
resolve(response);
})
.catch((errors) => {
setSubmitting(false);
});
}
};
Promise.all([saveMedia(), deleteMedia()]).then(
([savedMediaResponses]) => {
const mediaIds = savedMediaResponses.map((res) => res.data.media.id);
return saveItem(mediaIds);
}
},
);
},
});
const handleSubmitClick = useCallback(
(payload) => {
setPayload(payload);
handleSubmit();
},
[setPayload, handleSubmit],
);
const handleCancelClick = useCallback(
(payload) => {
onCancelForm && onCancelForm(payload);
},
[onCancelForm],
);
const accountItem = useCallback(
(item, { handleClick }) => (
<MenuItem
@@ -208,10 +258,9 @@ const ItemForm = ({
onClick={handleClick}
/>
),
[]
[],
);
// Filter Account Items
const filterAccounts = (query, account, _index, exactMatch) => {
const normalizedTitle = account.name.toLowerCase();
@@ -223,38 +272,38 @@ const ItemForm = ({
}
};
const onItemAccountSelect = useCallback((filedName) => {
return (account) => {
setFieldValue(filedName, account.id);
};
}, [setFieldValue]);
const onItemAccountSelect = useCallback(
(filedName) => {
return (account) => {
setFieldValue(filedName, account.id);
};
},
[setFieldValue],
);
const categoryItem = useCallback(
(item, { handleClick }) => (
<MenuItem text={item.name} onClick={handleClick} />
),
[]
[],
);
const requiredSpan = useMemo(() => <span class='required'>*</span>, []);
const infoIcon = useMemo(() => <Icon icon='info-circle' iconSize={12} />, []);
const requiredSpan = useMemo(() => <span class="required">*</span>, []);
const infoIcon = useMemo(() => <Icon icon="info-circle" iconSize={12} />, []);
const handleMoneyInputChange = (fieldKey) => (e, value) => {
setFieldValue(fieldKey, value);
};
const initialAttachmentFiles =useMemo(()=>{
const initialAttachmentFiles = useMemo(() => {
return itemDetail && itemDetail.media
? itemDetail.media.map((attach)=>({
preview:attach.attachment_file,
upload:true,
metadata:{...attach}
})):[];
},[itemDetail])
? itemDetail.media.map((attach) => ({
preview: attach.attachment_file,
upload: true,
metadata: { ...attach },
}))
: [];
}, [itemDetail]);
const handleDropFiles = useCallback((_files) => {
setFiles(_files.filter((file) => file.uploaded === false));
}, []);
@@ -267,20 +316,15 @@ const ItemForm = ({
}
});
},
[setDeletedFiles, deletedFiles,]
[setDeletedFiles, deletedFiles],
);
const handleCancelClickBtn = () => {
history.goBack();
};
return (
<div class='item-form'>
<div class="item-form">
<form onSubmit={handleSubmit}>
<div class='item-form__primary-section'>
<div class="item-form__primary-section">
<Row>
<Col xs={7}>
{/* Item type */}
<FormGroup
medium={true}
@@ -289,7 +333,7 @@ const ItemForm = ({
className={'form-group--item-type'}
intent={errors.type && touched.type && Intent.DANGER}
helperText={
<ErrorMessage {...{ errors, touched }} name='type' />
<ErrorMessage {...{ errors, touched }} name="type" />
}
inline={true}
>
@@ -299,7 +343,7 @@ const ItemForm = ({
{...getFieldProps('type')}
/>
</FormGroup>
{/* Item name */}
<FormGroup
label={<T id={'item_name'} />}
@@ -307,7 +351,7 @@ const ItemForm = ({
className={'form-group--item-name'}
intent={errors.name && touched.name && Intent.DANGER}
helperText={
<ErrorMessage {...{ errors, touched }} name='name' />
<ErrorMessage {...{ errors, touched }} name="name" />
}
inline={true}
>
@@ -324,7 +368,9 @@ const ItemForm = ({
labelInfo={infoIcon}
className={'form-group--item-sku'}
intent={errors.sku && touched.sku && Intent.DANGER}
helperText={<ErrorMessage {...{ errors, touched }} name='sku' />}
helperText={
<ErrorMessage {...{ errors, touched }} name="sku" />
}
inline={true}
>
<InputGroup
@@ -343,12 +389,12 @@ const ItemForm = ({
errors.category_id && touched.category_id && Intent.DANGER
}
helperText={
<ErrorMessage {...{ errors, touched }} name='category' />
<ErrorMessage {...{ errors, touched }} name="category" />
}
className={classNames(
'form-group--select-list',
'form-group--category',
Classes.FILL
Classes.FILL,
)}
>
<ListSelect
@@ -357,15 +403,13 @@ const ItemForm = ({
itemPredicate={filterAccounts}
popoverProps={{ minimal: true }}
onItemSelect={onItemAccountSelect('category_id')}
selectedItem={values.category_id}
selectedItemProp={'id'}
defaultText={<T id={'select_category'} />}
labelProp={'name'}
/>
</FormGroup>
{/* Active checkbox */}
<FormGroup
label={' '}
@@ -374,7 +418,7 @@ const ItemForm = ({
>
<Checkbox
inline={true}
label={<T id={'active'}/>}
label={<T id={'active'} />}
defaultChecked={values.active}
{...getFieldProps('active')}
/>
@@ -395,14 +439,18 @@ const ItemForm = ({
<Row gutterWidth={16} className={'item-form__accounts-section'}>
<Col width={404}>
<h4><T id={'purchase_information'}/></h4>
<h4>
<T id={'purchase_information'} />
</h4>
<FormGroup
label={<T id={'selling_price'}/>}
label={<T id={'selling_price'} />}
className={'form-group--item-selling-price'}
intent={errors.selling_price && touched.selling_price && Intent.DANGER}
intent={
errors.selling_price && touched.selling_price && Intent.DANGER
}
helperText={
<ErrorMessage {...{ errors, touched }} name='selling_price' />
<ErrorMessage {...{ errors, touched }} name="selling_price" />
}
inline={true}
>
@@ -419,7 +467,7 @@ const ItemForm = ({
}}
/>
</FormGroup>
{/* Selling account */}
<FormGroup
label={<T id={'account'} />}
@@ -431,12 +479,12 @@ const ItemForm = ({
Intent.DANGER
}
helperText={
<ErrorMessage {...{ errors, touched }} name='sell_account_id' />
<ErrorMessage {...{ errors, touched }} name="sell_account_id" />
}
className={classNames(
'form-group--sell-account',
'form-group--select-list',
Classes.FILL
Classes.FILL,
)}
>
<ListSelect
@@ -445,10 +493,8 @@ const ItemForm = ({
itemPredicate={filterAccounts}
popoverProps={{ minimal: true }}
onItemSelect={onItemAccountSelect('sell_account_id')}
selectedItem={values.sell_account_id}
selectedItemProp={'id'}
defaultText={<T id={'select_account'} />}
labelProp={'name'}
/>
@@ -456,7 +502,9 @@ const ItemForm = ({
</Col>
<Col width={404}>
<h4><T id={'sales_information'} /></h4>
<h4>
<T id={'sales_information'} />
</h4>
{/* Cost price */}
<FormGroup
@@ -464,7 +512,7 @@ const ItemForm = ({
className={'form-group--item-cost-price'}
intent={errors.cost_price && touched.cost_price && Intent.DANGER}
helperText={
<ErrorMessage {...{ errors, touched }} name='cost_price' />
<ErrorMessage {...{ errors, touched }} name="cost_price" />
}
inline={true}
>
@@ -490,12 +538,12 @@ const ItemForm = ({
Intent.DANGER
}
helperText={
<ErrorMessage {...{ errors, touched }} name='cost_account_id' />
<ErrorMessage {...{ errors, touched }} name="cost_account_id" />
}
className={classNames(
'form-group--cost-account',
'form-group--select-list',
Classes.FILL
Classes.FILL,
)}
>
<ListSelect
@@ -504,8 +552,7 @@ const ItemForm = ({
itemPredicate={filterAccounts}
popoverProps={{ minimal: true }}
onItemSelect={onItemAccountSelect('cost_account_id')}
defaultText={<T id={'select_account'} />}
defaultText={<T id={'select_account'} />}
labelProp={'name'}
selectedItem={values.cost_account_id}
selectedItemProp={'id'}
@@ -521,18 +568,23 @@ const ItemForm = ({
</h4>
<FormGroup
label={<T id={'inventory_account'}/>}
label={<T id={'inventory_account'} />}
inline={true}
intent={
errors.inventory_account_id &&
touched.inventory_account_id &&
Intent.DANGER
}
helperText={<ErrorMessage {...{ errors, touched }} name='inventory_account_id' />}
helperText={
<ErrorMessage
{...{ errors, touched }}
name="inventory_account_id"
/>
}
className={classNames(
'form-group--item-inventory_account',
'form-group--select-list',
Classes.FILL
Classes.FILL,
)}
>
<ListSelect
@@ -541,15 +593,15 @@ const ItemForm = ({
itemPredicate={filterAccounts}
popoverProps={{ minimal: true }}
onItemSelect={onItemAccountSelect('inventory_account_id')}
defaultText={<T id={'select_account'} />}
labelProp={'name'}
selectedItem={values.inventory_account_id}
selectedItemProp={'id'} />
selectedItemProp={'id'}
/>
</FormGroup>
<FormGroup
label={<T id={'opening_stock'}/>}
label={<T id={'opening_stock'} />}
className={'form-group--item-stock'}
inline={true}
>
@@ -561,28 +613,20 @@ const ItemForm = ({
</FormGroup>
</Col>
</Row>
<div class='form__floating-footer'>
<Button intent={Intent.PRIMARY} disabled={isSubmitting} type='submit'>
{ itemDetail && itemDetail.id ? <T id={'edit'}/> : <T id={'save'}/> }
</Button>
<Button className={'ml1'} disabled={isSubmitting}>
<T id={'save_as_draft'}/>
</Button>
<Button className={'ml1'} onClick={handleCancelClickBtn}>
<T id={'close'} />
</Button>
</div>
</form>
<ItemFloatingFooter
formik={isSubmitting}
onSubmitClick={handleSubmitClick}
onCancelClick={handleCancelClick}
itemDetail={itemDetail}
/>
</div>
);
};
export default compose(
withAccounts(({accounts})=>({
withAccounts(({ accounts }) => ({
accounts,
})),
withAccountDetail,

View File

@@ -1,5 +1,5 @@
import React, {useCallback } from 'react';
import { useParams,useHistory } from 'react-router-dom';
import React, { useCallback } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { useQuery } from 'react-query';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
@@ -12,7 +12,6 @@ import withItemsActions from './withItemsActions';
import { compose } from 'utils';
const ItemFormContainer = ({
// #withDashboardActions
changePageTitle,
@@ -29,35 +28,43 @@ const ItemFormContainer = ({
const { id } = useParams();
const history = useHistory();
const fetchAccounts = useQuery('accounts-list',
(key) => requestFetchAccounts());
const fetchAccounts = useQuery('accounts-list', (key) =>
requestFetchAccounts(),
);
const fetchCategories = useQuery('item-categories-list',
(key) => requestFetchItemCategories());
const fetchCategories = useQuery('item-categories-list', (key) =>
requestFetchItemCategories(),
);
const fetchItemDetail = useQuery(
id && ['item-detail-list', id],
(key) => requestFetchItems());
const fetchItemDetail = useQuery(id && ['item-detail-list', id], (key) =>
requestFetchItems(),
);
const handleFormSubmit =useCallback((payload)=>{
const handleFormSubmit = useCallback(
(payload) => {
payload.redirect && history.push('/items');
},
[history],
);
payload.redirect && history.push('/items/new');
},[history])
const handleCancel =useCallback(()=>{
history.push('/items/new');
},[history])
const handleCancel = useCallback(() => {
// history.push('/items');
history.goBack();
}, [history]);
return (
<DashboardInsider
loading={fetchItemDetail.isFetching || fetchAccounts.isFetching || fetchCategories.isFetching }
name={'item-form'}>
<ItemForm
itemId={id}
onFormSubmit={handleFormSubmit}
onCancelForm={handleCancel}
loading={
fetchItemDetail.isFetching ||
fetchAccounts.isFetching ||
fetchCategories.isFetching
}
name={'item-form'}
>
<ItemForm
onFormSubmit={handleFormSubmit}
itemId={id}
onCancelForm={handleCancel}
/>
</DashboardInsider>
);
@@ -67,5 +74,5 @@ export default compose(
withDashboardActions,
withAccountsActions,
withItemCategoriesActions,
withItemsActions
withItemsActions,
)(ItemFormContainer);

View File

@@ -0,0 +1,56 @@
import React from 'react';
import { Intent, Button } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
export default function ItemFloatingFooter({
formik: { isSubmitting },
onSubmitClick,
onCancelClick,
itemDetail,
}) {
return (
<div class="form__floating-footer">
<Button
intent={Intent.PRIMARY}
disabled={isSubmitting}
type="submit"
onClick={() => {
onSubmitClick({ publish: true, redirect: true });
}}
>
{itemDetail && itemDetail.id ? <T id={'edit'} /> : <T id={'save'} />}
</Button>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
className={'ml1'}
name={'save_and_new'}
onClick={() => {
onSubmitClick({ publish: true, redirect: false });
}}
>
<T id={'save_new'} />
</Button>
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={() => {
onSubmitClick({ publish: false, redirect: false });
}}
>
<T id={'save_as_draft'} />
</Button>
<Button
className={'ml1'}
onClick={() => {
onCancelClick && onCancelClick();
}}
>
<T id={'close'} />
</Button>
</div>
);
}

View File

@@ -438,6 +438,9 @@ export default {
'The expense has been successfully deleted',
the_expenses_has_been_successfully_deleted:
'The expenses has been successfully deleted',
once_delete_these_expenses_you_will_not_able_restore_them:
"Once you delete these expenses, you won't be able to retrieve them later. Are you sure you want to delete them?",
the_expense_id_has_been_published: 'The expense id has been published',
select_beneficiary_account: 'Select Beneficiary Account',
total_amount_equals_zero: 'Total amount equals zero',

View File

@@ -8,7 +8,7 @@ export const locale = {
notType: ({ path, type, value, originalValue }) => {
let isCast = originalValue != null && originalValue !== value;
let msg =
`${path} must beeeeee a \`${type}\` type, ` +
`${path} must be a \`${type}\` type, ` +
`but the final value was: \`${printValue(value, true)}\`` +
(isCast
? ` (cast from the value \`${printValue(originalValue, true)}\`).`

View File

@@ -107,3 +107,19 @@ export const deleteCustomer = ({ id }) => {
});
};
export const deleteBulkCustomers = ({ ids }) => {
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.delete('customers', { params: { ids } })
.then((response) => {
dispatch({
type: t.CUSTOMERS_BULK_DELETE,
payload: { ids },
});
resolve(response);
})
.catch((error) => {
reject(error.response.data.errors || []);
});
});
};

View File

@@ -40,6 +40,17 @@ const customersReducer = createReducer(initialState, {
const { loading } = action.payload;
state.loading = !!loading;
},
[t.CUSTOMERS_BULK_DELETE]: (state, action) => {
const { ids } = action.payload;
const items = { ...state.items };
ids.forEach((id) => {
if (typeof items[id] !== 'undefined') {
delete items[id];
}
});
state.items = items;
},
});
export default createTableQueryReducers('customers', customersReducer);

View File

@@ -4,5 +4,6 @@ export default {
CUSTOMERS_PAGE_SET: 'CUSTOMERS_PAGE_SET',
CUSTOMERS_TABLE_LOADING: 'CUSTOMERS_TABLE_LOADING',
CUSTOMERS_TABLE_QUERIES_ADD: 'CUSTOMERS_TABLE_QUERIES_ADD',
CUSTOMER_DELETE:'CUSTOMER_DELETE'
CUSTOMER_DELETE:'CUSTOMER_DELETE',
CUSTOMERS_BULK_DELETE:'CUSTOMERS_BULK_DELETE'
};

View File

@@ -114,7 +114,7 @@ export const deleteExpense = ({ id }) => {
export const deleteBulkExpenses = ({ ids }) => {
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.delete('expenses/bulk', { params: { ids } })
ApiService.delete('expenses', { params: { ids } })
.then((response) => {
dispatch({
type: t.EXPENSES_BULK_DELETE,

View File

@@ -2,93 +2,101 @@ import ApiService from 'services/ApiService';
import t from 'store/types';
export const submitItem = ({ form }) => {
return dispatch => ApiService.post(`items`, form);
return (dispatch) => ApiService.post(`items`, form);
};
export const editItem = ({ id, form }) => {
return dispatch => ApiService.post(`items/${id}`, form);
return (dispatch) => ApiService.post(`items/${id}`, form);
};
export const fetchItems = ({ query }) => {
return (dispatch, getState) => new Promise((resolve, reject) => {
const pageQuery = getState().items.tableQuery;
return (dispatch, getState) =>
new Promise((resolve, reject) => {
const pageQuery = getState().items.tableQuery;
dispatch({
type: t.ITEMS_TABLE_LOADING,
payload: { loading: true },
});
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.get(`items`, { params: { ...pageQuery, ...query } }).then(response => {
dispatch({
type: t.ITEMS_SET,
items: response.data.items.results,
});
dispatch({
type: t.ITEMS_PAGE_SET,
items: response.data.items.results,
customViewId: response.data.customViewId,
paginationMeta: response.data.items.pagination,
});
dispatch({
type: t.ITEMS_TABLE_LOADING,
payload: { loading: false },
payload: { loading: true },
});
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
resolve(response);
}).catch((error) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
reject(error);
ApiService.get(`items`, { params: { ...pageQuery, ...query } })
.then((response) => {
dispatch({
type: t.ITEMS_SET,
items: response.data.items.results,
});
dispatch({
type: t.ITEMS_PAGE_SET,
items: response.data.items.results,
customViewId: response.data.customViewId,
paginationMeta: response.data.items.pagination,
});
dispatch({
type: t.ITEMS_TABLE_LOADING,
payload: { loading: false },
});
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response);
})
.catch((error) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
reject(error);
});
});
});
};
export const fetchItem = ({ id }) => {
return dispatch =>
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.get(`items/${id}`)
.then(response => {
.then((response) => {
dispatch({
type: t.ITEM_SET,
item: response.data.item
item: response.data.item,
});
})
.catch(error => {
.catch((error) => {
reject(error);
});
});
};
export const deleteItem = ({ id }) => {
return dispatch => new Promise((resolve, reject) => {
ApiService.delete(`items/${id}`)
.then((response) => {
dispatch({
type: t.ITEM_DELETE,
payload: { id },
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.delete(`items/${id}`)
.then((response) => {
dispatch({
type: t.ITEM_DELETE,
payload: { id },
});
resolve(response);
})
.catch((error) => {
reject(error);
});
resolve(response);
}).catch((error) => { reject(error); });
});
});
};
export const deleteBulkItems = ({ ids }) => {
return dispatch => new Promise((resolve, reject) => {
ApiService.delete(`items`, { params: { ids }}).then((response) => {
dispatch({
type: t.ITEMS_BULK_DELETE,
payload: { ids }
});
resolve(response);
}).catch((error) => {
reject(error);
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.delete('items', { params: { ids } })
.then((response) => {
dispatch({
type: t.ITEMS_BULK_DELETE,
payload: { ids },
});
resolve(response);
})
.catch((error) => {
reject(error.response.data.errors || []);
});
});
});
};

View File

@@ -1,8 +1,6 @@
import t from 'store/types';
import { createReducer } from '@reduxjs/toolkit';
import {
getItemsViewPages,
} from 'store/items/items.selectors';
import { getItemsViewPages } from 'store/items/items.selectors';
import { createTableQueryReducers } from 'store/queryReducers';
const initialState = {
@@ -20,7 +18,7 @@ const itemsReducer = createReducer(initialState, {
[t.ITEMS_SET]: (state, action) => {
const _items = {};
action.items.forEach(item => {
action.items.forEach((item) => {
_items[item.id] = item;
});
state.items = {
@@ -43,11 +41,11 @@ const itemsReducer = createReducer(initialState, {
if (typeof itemRelation === 'undefined') {
state.itemsRelation[item.id] = [];
}
const filteredRelation = state.itemsRelation[item.id]
.filter((relation) => (
const filteredRelation = state.itemsRelation[item.id].filter(
(relation) =>
relation.viewId === viewId &&
relation.pageNumber === paginationMeta.page
));
relation.pageNumber === paginationMeta.page,
);
filteredRelation.push({
viewId,
@@ -61,10 +59,10 @@ const itemsReducer = createReducer(initialState, {
pages: {
...viewPages,
[paginationMeta.page]: {
ids: items.map(i => i.id),
ids: items.map((i) => i.id),
meta: paginationMeta,
},
},
},
};
},
@@ -93,9 +91,21 @@ const itemsReducer = createReducer(initialState, {
state.loading = !!loading;
},
[t.ITEMS_SET_CURRENT_VIEW]: (state, action) => {
[t.ITEMS_SET_CURRENT_VIEW]: (state, action) => {
state.currentViewId = action.currentViewId;
},
[t.ITEMS_BULK_DELETE]: (state, action) => {
const { ids } = action.payload;
const items = { ...state.items };
ids.forEach((id) => {
if (typeof items[id] !== 'undefined') {
delete items[id];
}
});
state.items = items;
},
});
export default createTableQueryReducers('items', itemsReducer);