From aac138aa1840eb5a033887e0bce8d89ee1aad285 Mon Sep 17 00:00:00 2001 From: elforjani3 Date: Wed, 24 Jun 2020 00:28:15 +0200 Subject: [PATCH] WIP / Feature & Fix Expense /Customer --- client/src/components/AccountsSelectList.js | 40 +- .../DataTableCells/AccountsListFieldCell.js | 41 ++- .../Accounting/MakeJournalEntriesPage.js | 34 +- client/src/containers/Customers/Customer.js | 24 +- .../containers/Customers/CustomerFooter.js | 57 +++ .../src/containers/Customers/CustomerForm.js | 93 ++--- .../src/containers/Customers/CustomersList.js | 12 +- .../Customers/withCustomerDetail.js | 2 +- .../Customers/withCustomersActions.js | 3 +- .../containers/Expenses/ExpenseDataTable.js | 49 ++- .../src/containers/Expenses/ExpenseFooter.js | 36 +- client/src/containers/Expenses/ExpenseForm.js | 83 +++-- .../containers/Expenses/ExpenseFormHeader.js | 10 +- .../src/containers/Expenses/ExpenseTable.js | 43 ++- client/src/containers/Expenses/Expenses.js | 11 +- .../src/containers/Expenses/ExpensesList.js | 8 +- .../containers/Expenses/withExpenseDetail.js | 2 +- .../containers/Items/ItemCategoriesList.js | 6 +- client/src/containers/Items/ItemForm.js | 344 ++++++++++-------- client/src/containers/Items/ItemFormPage.js | 59 +-- client/src/containers/Items/ItemsFooter.js | 56 +++ client/src/lang/en/index.js | 3 + client/src/lang/en/locale.js | 2 +- .../src/store/customers/customers.actions.js | 16 + .../src/store/customers/customers.reducer.js | 11 + client/src/store/customers/customers.type.js | 3 +- client/src/store/expenses/expenses.actions.js | 2 +- client/src/store/items/items.actions.js | 120 +++--- client/src/store/items/items.reducer.js | 32 +- 29 files changed, 762 insertions(+), 440 deletions(-) create mode 100644 client/src/containers/Customers/CustomerFooter.js create mode 100644 client/src/containers/Items/ItemsFooter.js diff --git a/client/src/components/AccountsSelectList.js b/client/src/components/AccountsSelectList.js index 374010f77..8054fed8f 100644 --- a/client/src/components/AccountsSelectList.js +++ b/client/src/components/AccountsSelectList.js @@ -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 ( - + ); }, []); - const onAccountSelect = useCallback((account) => { - setSelectedAccount({ ...account }); - onAccountSelected && onAccountSelected(account); - }, [setSelectedAccount, onAccountSelected]); + const onAccountSelect = useCallback( + (account) => { + setSelectedAccount({ ...account }); + onAccountSelected && onAccountSelected(account); + }, + [setSelectedAccount, onAccountSelected], + ); return ( ); -} \ No newline at end of file +} diff --git a/client/src/components/DataTableCells/AccountsListFieldCell.js b/client/src/components/DataTableCells/AccountsListFieldCell.js index 6cee2c770..c5853b4dd 100644 --- a/client/src/components/DataTableCells/AccountsListFieldCell.js +++ b/client/src/components/DataTableCells/AccountsListFieldCell.js @@ -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 ( + Classes.FILL, + )} + > + error={[account_id, expense_account_id]} + initialAccount={initialAccount} + /> ); }; -export default AccountCellRenderer; \ No newline at end of file +export default AccountCellRenderer; diff --git a/client/src/containers/Accounting/MakeJournalEntriesPage.js b/client/src/containers/Accounting/MakeJournalEntriesPage.js index 3fb6a708c..9b907201d 100644 --- a/client/src/containers/Accounting/MakeJournalEntriesPage.js +++ b/client/src/containers/Accounting/MakeJournalEntriesPage.js @@ -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'} + > + onCancelForm={handleCancel} + /> ); } @@ -64,4 +70,4 @@ export default compose( withAccountsActions, withCustomersActions, withManualJournalsActions, -)(MakeJournalEntriesPage); \ No newline at end of file +)(MakeJournalEntriesPage); diff --git a/client/src/containers/Customers/Customer.js b/client/src/containers/Customers/Customer.js index e4a8e237b..7d8b0f70f 100644 --- a/client/src/containers/Customers/Customer.js +++ b/client/src/containers/Customers/Customer.js @@ -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 ( - + ); } diff --git a/client/src/containers/Customers/CustomerFooter.js b/client/src/containers/Customers/CustomerFooter.js new file mode 100644 index 000000000..f1c0aaf51 --- /dev/null +++ b/client/src/containers/Customers/CustomerFooter.js @@ -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 ( +
+ + + + + + + +
+ ); +} diff --git a/client/src/containers/Customers/CustomerForm.js b/client/src/containers/Customers/CustomerForm.js index 4de5b6b47..f535623ac 100644 --- a/client/src/containers/Customers/CustomerForm.js +++ b/client/src/containers/Customers/CustomerForm.js @@ -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 (
@@ -396,34 +421,14 @@ function CustomerForm({ - - + +
); } diff --git a/client/src/containers/Customers/CustomersList.js b/client/src/containers/Customers/CustomersList.js index 7c2098c3c..9d47c649b 100644 --- a/client/src/containers/Customers/CustomersList.js +++ b/client/src/containers/Customers/CustomersList.js @@ -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]); diff --git a/client/src/containers/Customers/withCustomerDetail.js b/client/src/containers/Customers/withCustomerDetail.js index 755c2fc1a..bd7cc8a65 100644 --- a/client/src/containers/Customers/withCustomerDetail.js +++ b/client/src/containers/Customers/withCustomerDetail.js @@ -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); diff --git a/client/src/containers/Customers/withCustomersActions.js b/client/src/containers/Customers/withCustomersActions.js index 66900f4f8..590004dd3 100644 --- a/client/src/containers/Customers/withCustomersActions.js +++ b/client/src/containers/Customers/withCustomersActions.js @@ -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 })), diff --git a/client/src/containers/Expenses/ExpenseDataTable.js b/client/src/containers/Expenses/ExpenseDataTable.js index bc5579a0d..0aeaad5b0 100644 --- a/client/src/containers/Expenses/ExpenseDataTable.js +++ b/client/src/containers/Expenses/ExpenseDataTable.js @@ -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) => ( - + - + ), - [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 => - (
- {category.expense_account.name} ${ category.amount }
) + 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) => ( +
+ - {category.expense_account.name} ${category.amount} +
+ )); + return ( + {'- Multi Categories -'} ); - return { '- Multi Categories -' }; } - } + }; const columns = useMemo( () => [ @@ -179,7 +190,7 @@ function ExpenseDataTable({ id: 'publish', Header: formatMessage({ id: 'publish' }), accessor: (r) => { - return !r.published ? ( + return r.published ? ( diff --git a/client/src/containers/Expenses/ExpenseFooter.js b/client/src/containers/Expenses/ExpenseFooter.js index 8bd219518..d1506b9ff 100644 --- a/client/src/containers/Expenses/ExpenseFooter.js +++ b/client/src/containers/Expenses/ExpenseFooter.js @@ -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 (
+ + +
); -}; +} diff --git a/client/src/containers/Expenses/ExpenseForm.js b/client/src/containers/Expenses/ExpenseForm.js index 3bc924c34..b9f7bb559 100644 --- a/client/src/containers/Expenses/ExpenseForm.js +++ b/client/src/containers/Expenses/ExpenseForm.js @@ -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'} /> - - + ); } diff --git a/client/src/containers/Expenses/ExpenseFormHeader.js b/client/src/containers/Expenses/ExpenseFormHeader.js index 5c2baf532..98843db77 100644 --- a/client/src/containers/Expenses/ExpenseFormHeader.js +++ b/client/src/containers/Expenses/ExpenseFormHeader.js @@ -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({ } - 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, diff --git a/client/src/containers/Expenses/ExpenseTable.js b/client/src/containers/Expenses/ExpenseTable.js index a4d06926c..733ac2912 100644 --- a/client/src/containers/Expenses/ExpenseTable.js +++ b/client/src/containers/Expenses/ExpenseTable.js @@ -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 ( {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' }) }), + Header: ( + <> + {formatMessage({ id: 'expense_category' })} + + + ), 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], ); diff --git a/client/src/containers/Expenses/Expenses.js b/client/src/containers/Expenses/Expenses.js index f372fa43e..b6643109a 100644 --- a/client/src/containers/Expenses/Expenses.js +++ b/client/src/containers/Expenses/Expenses.js @@ -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'} > requestFetchExpensesTable(), ); @@ -210,7 +210,7 @@ function ExpensesList({

- {/* } confirmButtonText={ @@ -223,10 +223,10 @@ function ExpensesList({ >

-
*/} + ); diff --git a/client/src/containers/Expenses/withExpenseDetail.js b/client/src/containers/Expenses/withExpenseDetail.js index 62a44c352..4644b5510 100644 --- a/client/src/containers/Expenses/withExpenseDetail.js +++ b/client/src/containers/Expenses/withExpenseDetail.js @@ -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); diff --git a/client/src/containers/Items/ItemCategoriesList.js b/client/src/containers/Items/ItemCategoriesList.js index 12f7bbd6d..c7d229d1c 100644 --- a/client/src/containers/Items/ItemCategoriesList.js +++ b/client/src/containers/Items/ItemCategoriesList.js @@ -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 = ({ >

diff --git a/client/src/containers/Items/ItemForm.js b/client/src/containers/Items/ItemForm.js index a3d26f832..e3b864c58 100644 --- a/client/src/containers/Items/ItemForm.js +++ b/client/src/containers/Items/ItemForm.js @@ -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 }) => ( ), - [] + [], ); - // 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 }) => ( ), - [] + [], ); - const requiredSpan = useMemo(() => *, []); - const infoIcon = useMemo(() => , []); + const requiredSpan = useMemo(() => *, []); + const infoIcon = useMemo(() => , []); 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 ( -
+
-
+
- {/* Item type */} + } inline={true} > @@ -299,7 +343,7 @@ const ItemForm = ({ {...getFieldProps('type')} /> - + {/* Item name */} } @@ -307,7 +351,7 @@ const ItemForm = ({ className={'form-group--item-name'} intent={errors.name && touched.name && Intent.DANGER} helperText={ - + } inline={true} > @@ -324,7 +368,9 @@ const ItemForm = ({ labelInfo={infoIcon} className={'form-group--item-sku'} intent={errors.sku && touched.sku && Intent.DANGER} - helperText={} + helperText={ + + } inline={true} > + } className={classNames( 'form-group--select-list', 'form-group--category', - Classes.FILL + Classes.FILL, )} > } labelProp={'name'} /> - + {/* Active checkbox */} } + label={} defaultChecked={values.active} {...getFieldProps('active')} /> @@ -395,14 +439,18 @@ const ItemForm = ({ -

+

+ +

} + label={} 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={ - + } inline={true} > @@ -419,7 +467,7 @@ const ItemForm = ({ }} /> - + {/* Selling account */} } @@ -431,12 +479,12 @@ const ItemForm = ({ Intent.DANGER } helperText={ - + } className={classNames( 'form-group--sell-account', 'form-group--select-list', - Classes.FILL + Classes.FILL, )} > } labelProp={'name'} /> @@ -456,7 +502,9 @@ const ItemForm = ({ -

+

+ +

{/* Cost price */} + } inline={true} > @@ -490,12 +538,12 @@ const ItemForm = ({ Intent.DANGER } helperText={ - + } className={classNames( 'form-group--cost-account', 'form-group--select-list', - Classes.FILL + Classes.FILL, )} > } + defaultText={} labelProp={'name'} selectedItem={values.cost_account_id} selectedItemProp={'id'} @@ -521,18 +568,23 @@ const ItemForm = ({ } + label={} inline={true} intent={ errors.inventory_account_id && touched.inventory_account_id && Intent.DANGER } - helperText={} + helperText={ + + } className={classNames( 'form-group--item-inventory_account', 'form-group--select-list', - Classes.FILL + Classes.FILL, )} > } labelProp={'name'} selectedItem={values.inventory_account_id} - selectedItemProp={'id'} /> + selectedItemProp={'id'} + /> } + label={} className={'form-group--item-stock'} inline={true} > @@ -561,28 +613,20 @@ const ItemForm = ({
- - + +
); }; export default compose( - withAccounts(({accounts})=>({ + withAccounts(({ accounts }) => ({ accounts, })), withAccountDetail, diff --git a/client/src/containers/Items/ItemFormPage.js b/client/src/containers/Items/ItemFormPage.js index 71b95cb9c..2f36f96a8 100644 --- a/client/src/containers/Items/ItemFormPage.js +++ b/client/src/containers/Items/ItemFormPage.js @@ -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 ( - + ); @@ -67,5 +74,5 @@ export default compose( withDashboardActions, withAccountsActions, withItemCategoriesActions, - withItemsActions + withItemsActions, )(ItemFormContainer); diff --git a/client/src/containers/Items/ItemsFooter.js b/client/src/containers/Items/ItemsFooter.js new file mode 100644 index 000000000..57b0e9386 --- /dev/null +++ b/client/src/containers/Items/ItemsFooter.js @@ -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 ( + + ); +} diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js index e00255fa8..82c698fbf 100644 --- a/client/src/lang/en/index.js +++ b/client/src/lang/en/index.js @@ -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', diff --git a/client/src/lang/en/locale.js b/client/src/lang/en/locale.js index 47ba9bba1..395a78ccc 100644 --- a/client/src/lang/en/locale.js +++ b/client/src/lang/en/locale.js @@ -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)}\`).` diff --git a/client/src/store/customers/customers.actions.js b/client/src/store/customers/customers.actions.js index ab705bc92..6e2a131a5 100644 --- a/client/src/store/customers/customers.actions.js +++ b/client/src/store/customers/customers.actions.js @@ -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 || []); + }); + }); +}; diff --git a/client/src/store/customers/customers.reducer.js b/client/src/store/customers/customers.reducer.js index 86d198974..a0ea60a8e 100644 --- a/client/src/store/customers/customers.reducer.js +++ b/client/src/store/customers/customers.reducer.js @@ -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); diff --git a/client/src/store/customers/customers.type.js b/client/src/store/customers/customers.type.js index c63620434..81b12d864 100644 --- a/client/src/store/customers/customers.type.js +++ b/client/src/store/customers/customers.type.js @@ -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' }; diff --git a/client/src/store/expenses/expenses.actions.js b/client/src/store/expenses/expenses.actions.js index 1f4ac1097..ff271f504 100644 --- a/client/src/store/expenses/expenses.actions.js +++ b/client/src/store/expenses/expenses.actions.js @@ -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, diff --git a/client/src/store/items/items.actions.js b/client/src/store/items/items.actions.js index 461bd949b..963890492 100644 --- a/client/src/store/items/items.actions.js +++ b/client/src/store/items/items.actions.js @@ -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 || []); + }); }); - }); }; diff --git a/client/src/store/items/items.reducer.js b/client/src/store/items/items.reducer.js index 25fecd60e..5ea3a6c55 100644 --- a/client/src/store/items/items.reducer.js +++ b/client/src/store/items/items.reducer.js @@ -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);