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

View File

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

View File

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

View File

@@ -25,15 +25,31 @@ function Customer({
requestFetchCustomers({}), 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 ( return (
<DashboardInsider <DashboardInsider
// formik={formik} loading={fetchCustomerDatails.isFetching || fetchCustomers.isFetching}
loading={ fetchCustomerDatails.isFetching || fetchCustomers.isFetching}
name={'customer-form'} name={'customer-form'}
> >
<CustomerForm customerId={id} /> <CustomerForm
onFormSubmit={handleFormSubmit}
customerId={id}
onCancelForm={handleCancel}
/>
</DashboardInsider> </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 useMedia from 'hooks/useMedia';
import { compose } from 'utils'; import { compose } from 'utils';
import CustomerFloatingFooter from './CustomerFooter';
function CustomerForm({ function CustomerForm({
// #withDashboardActions // #withDashboardActions
@@ -40,7 +41,7 @@ function CustomerForm({
customers, customers,
//#withCustomerDetail //#withCustomerDetail
customerDetail, customer,
//#withCustomersActions //#withCustomersActions
requestSubmitCustomer, requestSubmitCustomer,
@@ -50,9 +51,13 @@ function CustomerForm({
// #withMediaActions // #withMediaActions
requestSubmitMedia, requestSubmitMedia,
requestDeleteMedia, requestDeleteMedia,
//#Props
onFormSubmit,
onCancelForm,
}) { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const history = useHistory(); const history = useHistory();
const [payload, setPayload] = useState({});
const { const {
setFiles, setFiles,
@@ -127,22 +132,30 @@ function CustomerForm({
const initialValues = useMemo( const initialValues = useMemo(
() => ({ () => ({
...(customerDetail ...(customer
? { ? {
...pick(customerDetail, Object.keys(defaultInitialValues)), ...pick(customer, Object.keys(defaultInitialValues)),
} }
: { : {
...defaultInitialValues, ...defaultInitialValues,
}), }),
}), }),
[customerDetail, defaultInitialValues], [customer, defaultInitialValues],
);
const saveInvokeSubmit = useCallback(
(payload) => {
onFormSubmit && onFormSubmit(payload);
},
[onFormSubmit],
); );
useEffect(() => { useEffect(() => {
customerDetail && customerDetail.id customer && customer.id
? changePageTitle(formatMessage({ id: 'edit_customer_details' })) ? changePageTitle(formatMessage({ id: 'edit_customer_details' }))
: changePageTitle(formatMessage({ id: 'new_customer' })); : changePageTitle(formatMessage({ id: 'new_customer' }));
}, [changePageTitle, customerDetail, formatMessage]); }, [changePageTitle, customer, formatMessage]);
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
@@ -152,9 +165,9 @@ function CustomerForm({
}, },
onSubmit: (values, { setSubmitting, resetForm, setErrors }) => { onSubmit: (values, { setSubmitting, resetForm, setErrors }) => {
const formValues = { ...values }; const formValues = { ...values, status: payload.publish };
if (customerDetail && customerDetail.id) { if (customer && customer.id) {
requestEditCustomer(customerDetail.id, formValues) requestEditCustomer(customer.id, formValues)
.then((response) => { .then((response) => {
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage({
@@ -163,8 +176,9 @@ function CustomerForm({
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
setSubmitting(false); setSubmitting(false);
history.push('/customers'); // history.push('/customers');
resetForm(); resetForm();
saveInvokeSubmit({ action: 'update', ...payload });
}) })
.catch((errors) => { .catch((errors) => {
setSubmitting(false); setSubmitting(false);
@@ -178,7 +192,9 @@ function CustomerForm({
}), }),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
history.push('/customers'); // history.push('/customers');
setSubmitting(false);
saveInvokeSubmit({ action: 'new', ...payload });
}) })
.catch((errors) => { .catch((errors) => {
setSubmitting(false); setSubmitting(false);
@@ -202,8 +218,8 @@ function CustomerForm({
); );
const initialAttachmentFiles = useMemo(() => { const initialAttachmentFiles = useMemo(() => {
return customerDetail && customerDetail.media return customer && customer.media
? customerDetail.media.map((attach) => ({ ? customer.media.map((attach) => ({
preview: attach.attachment_file, preview: attach.attachment_file,
upload: true, upload: true,
metadata: { ...attach }, metadata: { ...attach },
@@ -224,11 +240,20 @@ function CustomerForm({
}, },
[setDeletedFiles, deletedFiles], [setDeletedFiles, deletedFiles],
); );
const handleSubmitClick = useCallback(
(payload) => {
setPayload(payload);
formik.handleSubmit();
},
[setPayload, formik],
);
const handleCancelClickBtn = () => { const handleCancelClick = useCallback(
history.goBack(); (payload) => {
}; onCancelForm && onCancelForm(payload);
},
[onCancelForm],
);
return ( return (
<div className={'customer-form'}> <div className={'customer-form'}>
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
@@ -396,34 +421,14 @@ function CustomerForm({
</FormGroup> </FormGroup>
<CustomersTabs formik={formik} /> <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> </form>
<CustomerFloatingFooter
formik={formik}
onSubmitClick={handleSubmitClick}
customer={customer}
onCancelClick={handleCancelClick}
/>
</div> </div>
); );
} }

View File

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

View File

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

View File

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

View File

@@ -98,10 +98,9 @@ function ExpenseDataTable({
const actionMenuList = useCallback( const actionMenuList = useCallback(
(expense) => ( (expense) => (
<Menu> <Menu>
<MenuItem <MenuItem text={formatMessage({ id: 'view_details' })} />
text={formatMessage({ id: 'view_details' })} />
<MenuDivider /> <MenuDivider />
<If condition={expenses.published}> <If condition={!expense.published}>
<MenuItem <MenuItem
text={formatMessage({ id: 'publish_expense' })} text={formatMessage({ id: 'publish_expense' })}
onClick={handlePublishExpense(expense)} onClick={handlePublishExpense(expense)}
@@ -119,23 +118,35 @@ function ExpenseDataTable({
/> />
</Menu> </Menu>
), ),
[handleEditExpense, handleDeleteExpense, handlePublishExpense, formatMessage], [
handleEditExpense,
handleDeleteExpense,
handlePublishExpense,
formatMessage,
],
); );
const onRowContextMenu = useCallback((cell) => { const onRowContextMenu = useCallback(
(cell) => {
return actionMenuList(cell.row.original); return actionMenuList(cell.row.original);
}, [actionMenuList]); },
[actionMenuList],
const expenseAccountAccessor = (expense) => { );
if (expense.categories.length === 1) {
return expense.categories[0].expense_account.name; const expenseAccountAccessor = (_expense) => {
} else if (expense.categories.length > 1) { if (_expense.categories.length === 1) {
const mutliCategories = expense.categories.map(category => return _expense.categories[0].expense_account.name;
(<div>- {category.expense_account.name} ${ category.amount }</div>) } 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( const columns = useMemo(
() => [ () => [
@@ -179,7 +190,7 @@ function ExpenseDataTable({
id: 'publish', id: 'publish',
Header: formatMessage({ id: 'publish' }), Header: formatMessage({ id: 'publish' }),
accessor: (r) => { accessor: (r) => {
return !r.published ? ( return r.published ? (
<Tag minimal={true}> <Tag minimal={true}>
<T id={'published'} /> <T id={'published'} />
</Tag> </Tag>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import React, {useCallback } from 'react'; import React, { useCallback } from 'react';
import { useParams,useHistory } from 'react-router-dom'; import { useParams, useHistory } from 'react-router-dom';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import DashboardInsider from 'components/Dashboard/DashboardInsider'; import DashboardInsider from 'components/Dashboard/DashboardInsider';
@@ -12,7 +12,6 @@ import withItemsActions from './withItemsActions';
import { compose } from 'utils'; import { compose } from 'utils';
const ItemFormContainer = ({ const ItemFormContainer = ({
// #withDashboardActions // #withDashboardActions
changePageTitle, changePageTitle,
@@ -29,34 +28,42 @@ const ItemFormContainer = ({
const { id } = useParams(); const { id } = useParams();
const history = useHistory(); const history = useHistory();
const fetchAccounts = useQuery('accounts-list', const fetchAccounts = useQuery('accounts-list', (key) =>
(key) => requestFetchAccounts()); requestFetchAccounts(),
);
const fetchCategories = useQuery('item-categories-list', const fetchCategories = useQuery('item-categories-list', (key) =>
(key) => requestFetchItemCategories()); requestFetchItemCategories(),
);
const fetchItemDetail = useQuery( const fetchItemDetail = useQuery(id && ['item-detail-list', id], (key) =>
id && ['item-detail-list', id], requestFetchItems(),
(key) => requestFetchItems()); );
const handleFormSubmit =useCallback((payload)=>{ const handleFormSubmit = useCallback(
(payload) => {
payload.redirect && history.push('/items');
},
[history],
);
payload.redirect && history.push('/items/new'); const handleCancel = useCallback(() => {
// history.push('/items');
},[history]) history.goBack();
}, [history]);
const handleCancel =useCallback(()=>{
history.push('/items/new');
},[history])
return ( return (
<DashboardInsider <DashboardInsider
loading={fetchItemDetail.isFetching || fetchAccounts.isFetching || fetchCategories.isFetching } loading={
name={'item-form'}> fetchItemDetail.isFetching ||
fetchAccounts.isFetching ||
fetchCategories.isFetching
}
name={'item-form'}
>
<ItemForm <ItemForm
itemId={id}
onFormSubmit={handleFormSubmit} onFormSubmit={handleFormSubmit}
itemId={id}
onCancelForm={handleCancel} onCancelForm={handleCancel}
/> />
</DashboardInsider> </DashboardInsider>
@@ -67,5 +74,5 @@ export default compose(
withDashboardActions, withDashboardActions,
withAccountsActions, withAccountsActions,
withItemCategoriesActions, withItemCategoriesActions,
withItemsActions withItemsActions,
)(ItemFormContainer); )(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 expense has been successfully deleted',
the_expenses_has_been_successfully_deleted: the_expenses_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', the_expense_id_has_been_published: 'The expense id has been published',
select_beneficiary_account: 'Select Beneficiary Account', select_beneficiary_account: 'Select Beneficiary Account',
total_amount_equals_zero: 'Total amount equals zero', total_amount_equals_zero: 'Total amount equals zero',

View File

@@ -8,7 +8,7 @@ export const locale = {
notType: ({ path, type, value, originalValue }) => { notType: ({ path, type, value, originalValue }) => {
let isCast = originalValue != null && originalValue !== value; let isCast = originalValue != null && originalValue !== value;
let msg = let msg =
`${path} must beeeeee a \`${type}\` type, ` + `${path} must be a \`${type}\` type, ` +
`but the final value was: \`${printValue(value, true)}\`` + `but the final value was: \`${printValue(value, true)}\`` +
(isCast (isCast
? ` (cast from the value \`${printValue(originalValue, true)}\`).` ? ` (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; const { loading } = action.payload;
state.loading = !!loading; 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); export default createTableQueryReducers('customers', customersReducer);

View File

@@ -4,5 +4,6 @@ export default {
CUSTOMERS_PAGE_SET: 'CUSTOMERS_PAGE_SET', CUSTOMERS_PAGE_SET: 'CUSTOMERS_PAGE_SET',
CUSTOMERS_TABLE_LOADING: 'CUSTOMERS_TABLE_LOADING', CUSTOMERS_TABLE_LOADING: 'CUSTOMERS_TABLE_LOADING',
CUSTOMERS_TABLE_QUERIES_ADD: 'CUSTOMERS_TABLE_QUERIES_ADD', 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 }) => { export const deleteBulkExpenses = ({ ids }) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
ApiService.delete('expenses/bulk', { params: { ids } }) ApiService.delete('expenses', { params: { ids } })
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: t.EXPENSES_BULK_DELETE, type: t.EXPENSES_BULK_DELETE,

View File

@@ -2,15 +2,16 @@ import ApiService from 'services/ApiService';
import t from 'store/types'; import t from 'store/types';
export const submitItem = ({ form }) => { export const submitItem = ({ form }) => {
return dispatch => ApiService.post(`items`, form); return (dispatch) => ApiService.post(`items`, form);
}; };
export const editItem = ({ id, form }) => { export const editItem = ({ id, form }) => {
return dispatch => ApiService.post(`items/${id}`, form); return (dispatch) => ApiService.post(`items/${id}`, form);
}; };
export const fetchItems = ({ query }) => { export const fetchItems = ({ query }) => {
return (dispatch, getState) => new Promise((resolve, reject) => { return (dispatch, getState) =>
new Promise((resolve, reject) => {
const pageQuery = getState().items.tableQuery; const pageQuery = getState().items.tableQuery;
dispatch({ dispatch({
@@ -20,7 +21,8 @@ export const fetchItems = ({ query }) => {
dispatch({ dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING, type: t.SET_DASHBOARD_REQUEST_LOADING,
}); });
ApiService.get(`items`, { params: { ...pageQuery, ...query } }).then(response => { ApiService.get(`items`, { params: { ...pageQuery, ...query } })
.then((response) => {
dispatch({ dispatch({
type: t.ITEMS_SET, type: t.ITEMS_SET,
items: response.data.items.results, items: response.data.items.results,
@@ -39,7 +41,8 @@ export const fetchItems = ({ query }) => {
type: t.SET_DASHBOARD_REQUEST_COMPLETED, type: t.SET_DASHBOARD_REQUEST_COMPLETED,
}); });
resolve(response); resolve(response);
}).catch((error) => { })
.catch((error) => {
dispatch({ dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED, type: t.SET_DASHBOARD_REQUEST_COMPLETED,
}); });
@@ -49,23 +52,24 @@ export const fetchItems = ({ query }) => {
}; };
export const fetchItem = ({ id }) => { export const fetchItem = ({ id }) => {
return dispatch => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
ApiService.get(`items/${id}`) ApiService.get(`items/${id}`)
.then(response => { .then((response) => {
dispatch({ dispatch({
type: t.ITEM_SET, type: t.ITEM_SET,
item: response.data.item item: response.data.item,
}); });
}) })
.catch(error => { .catch((error) => {
reject(error); reject(error);
}); });
}); });
}; };
export const deleteItem = ({ id }) => { export const deleteItem = ({ id }) => {
return dispatch => new Promise((resolve, reject) => { return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.delete(`items/${id}`) ApiService.delete(`items/${id}`)
.then((response) => { .then((response) => {
dispatch({ dispatch({
@@ -73,22 +77,26 @@ export const deleteItem = ({ id }) => {
payload: { id }, payload: { id },
}); });
resolve(response); resolve(response);
}).catch((error) => { reject(error); }); })
}); .catch((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); 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.response.data.errors || []);
});
});
};

View File

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