mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
feature : Puschases & Sales / fix : tasks
This commit is contained in:
@@ -9,6 +9,7 @@ export default function AccountsSelectList({
|
||||
selectedAccountId,
|
||||
defaultSelectText = 'Select account',
|
||||
onAccountSelected,
|
||||
disabled = false,
|
||||
}) {
|
||||
// Find initial account object to set it as default account in initial render.
|
||||
const initialAccount = useMemo(
|
||||
@@ -77,6 +78,7 @@ export default function AccountsSelectList({
|
||||
onItemSelect={onAccountSelect}
|
||||
>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
text={selectedAccount ? selectedAccount.name : defaultSelectText}
|
||||
/>
|
||||
</Select>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
export default function DivFieldCell({ cell: { value: initialValue } }) {
|
||||
export const DivFieldCell = ({ cell: { value: initialValue } }) => {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -8,4 +8,13 @@ export default function DivFieldCell({ cell: { value: initialValue } }) {
|
||||
}, [initialValue]);
|
||||
|
||||
return <div>${value}</div>;
|
||||
}
|
||||
};
|
||||
export const EmptyDiv = ({ cell: { value: initialValue } }) => {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
return <div>{value}</div>;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import InputGroupCell from './InputGroupCell';
|
||||
import ContactsListFieldCell from './ContactsListFieldCell';
|
||||
import EstimatesListFieldCell from './EstimatesListFieldCell';
|
||||
import PercentFieldCell from './PercentFieldCell';
|
||||
import DivFieldCell from './DivFieldCell';
|
||||
import { DivFieldCell,EmptyDiv } from './DivFieldCell';
|
||||
export {
|
||||
AccountsListFieldCell,
|
||||
MoneyFieldCell,
|
||||
@@ -13,4 +13,5 @@ export {
|
||||
EstimatesListFieldCell,
|
||||
PercentFieldCell,
|
||||
DivFieldCell,
|
||||
EmptyDiv,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {useState, useMemo, useEffect, useCallback} from 'react';
|
||||
import {InputGroup} from '@blueprintjs/core';
|
||||
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { InputGroup } from '@blueprintjs/core';
|
||||
|
||||
const joinIntegerAndDecimal = (integer, decimal, separator) => {
|
||||
let output = `${integer}`;
|
||||
@@ -16,13 +16,13 @@ const hasSeparator = (input, separator) => {
|
||||
};
|
||||
|
||||
const addThousandSeparator = (integer, separator) => {
|
||||
return integer.replace(/(\d)(?=(?:\d{3})+\b)/gm, `$1${separator}`)
|
||||
return integer.replace(/(\d)(?=(?:\d{3})+\b)/gm, `$1${separator}`);
|
||||
};
|
||||
|
||||
const toString = (number) => `${number}`;
|
||||
|
||||
const onlyNumbers = (input) => {
|
||||
return toString(input).replace(/\D+/g, '') || '0'
|
||||
return toString(input).replace(/\D+/g, '') || '0';
|
||||
};
|
||||
|
||||
const formatter = (value, options) => {
|
||||
@@ -31,11 +31,19 @@ const formatter = (value, options) => {
|
||||
const parts = toString(input).split(options.decimal);
|
||||
const integer = parseInt(onlyNumbers(parts[0]), 10);
|
||||
const decimal = parts[1] ? onlyNumbers(parts[1]) : null;
|
||||
const integerThousand = addThousandSeparator(toString(integer), options.thousands);
|
||||
const integerThousand = addThousandSeparator(
|
||||
toString(integer),
|
||||
options.thousands,
|
||||
);
|
||||
const separator = hasSeparator(input, options.decimal)
|
||||
? options.decimal : false;
|
||||
? options.decimal
|
||||
: false;
|
||||
|
||||
return `${navigate}${options.prefix}${joinIntegerAndDecimal(integerThousand, decimal, separator)}${options.suffix}`;
|
||||
return `${navigate}${options.prefix}${joinIntegerAndDecimal(
|
||||
integerThousand,
|
||||
decimal,
|
||||
separator,
|
||||
)}${options.suffix}`;
|
||||
};
|
||||
|
||||
const unformatter = (input, options) => {
|
||||
@@ -44,12 +52,12 @@ const unformatter = (input, options) => {
|
||||
const integer = parseInt(onlyNumbers(parts[0]), 10);
|
||||
const decimal = parts[1] ? onlyNumbers(parts[1]) : null;
|
||||
const separator = hasSeparator(input, options.decimal)
|
||||
? options.decimal : false;
|
||||
? options.decimal
|
||||
: false;
|
||||
|
||||
return `${navigate}${joinIntegerAndDecimal(integer, decimal, separator)}`;
|
||||
};
|
||||
|
||||
|
||||
export default function MoneyFieldGroup({
|
||||
value,
|
||||
prefix = '',
|
||||
@@ -59,32 +67,43 @@ export default function MoneyFieldGroup({
|
||||
precision = 2,
|
||||
inputGroupProps,
|
||||
onChange,
|
||||
disabled = false,
|
||||
}) {
|
||||
const [state, setState] = useState(value);
|
||||
|
||||
const options = useMemo(() => ({
|
||||
prefix, suffix, thousands, decimal, precision,
|
||||
}), [
|
||||
prefix, suffix, thousands, decimal, precision,
|
||||
]);
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
prefix,
|
||||
suffix,
|
||||
thousands,
|
||||
decimal,
|
||||
precision,
|
||||
}),
|
||||
[prefix, suffix, thousands, decimal, precision],
|
||||
);
|
||||
|
||||
const handleChange = useCallback((event) => {
|
||||
const formatted = formatter(event.target.value, options);
|
||||
const value = unformatter(event.target.value, options);
|
||||
const handleChange = useCallback(
|
||||
(event) => {
|
||||
const formatted = formatter(event.target.value, options);
|
||||
const value = unformatter(event.target.value, options);
|
||||
|
||||
setState(formatted);
|
||||
onChange && onChange(event, value);
|
||||
}, [onChange, options]);
|
||||
setState(formatted);
|
||||
onChange && onChange(event, value);
|
||||
},
|
||||
[onChange, options],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const formatted = formatter(value, options);
|
||||
setState(formatted)
|
||||
setState(formatted);
|
||||
}, [value, options, setState]);
|
||||
|
||||
return (
|
||||
<InputGroup
|
||||
<InputGroup
|
||||
value={state}
|
||||
onChange={handleChange}
|
||||
{...inputGroupProps} />
|
||||
{...inputGroupProps}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,37 +40,25 @@ export default [
|
||||
children: [
|
||||
{
|
||||
text: <T id={'estimates'} />,
|
||||
href: '/estimates/new',
|
||||
href: '/estimates',
|
||||
},
|
||||
// {
|
||||
// text: <T id={'estimate_list'} />,
|
||||
// href: '/estimates',
|
||||
// },
|
||||
|
||||
{
|
||||
text: <T id={'invocies'} />,
|
||||
href: '/invoices/new',
|
||||
href: '/invoices',
|
||||
},
|
||||
// {
|
||||
// text: <T id={'invoices_list'} />,
|
||||
// href: '/invoices',
|
||||
// },
|
||||
|
||||
{
|
||||
text: <T id={'payment_receives'} />,
|
||||
href: '/payment-receive/new',
|
||||
href: '/payment-receives',
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
text: <T id={'invoices_list'} />,
|
||||
href: '/invoices',
|
||||
},
|
||||
{
|
||||
text: <T id={'receipts'} />,
|
||||
href: '/receipts/new',
|
||||
href: '/receipts',
|
||||
},
|
||||
// {
|
||||
// text: <T id={'receipt_list'} />,
|
||||
// href: '/receipts',
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -78,14 +66,12 @@ export default [
|
||||
children: [
|
||||
{
|
||||
text: <T id={'bills'} />,
|
||||
href: '/bills/new',
|
||||
href: '/bills',
|
||||
},
|
||||
// {
|
||||
// text: <T id={'bill_list'} />,
|
||||
// href: '/bills',
|
||||
// },
|
||||
|
||||
{
|
||||
text: <T id={'payment_mades'} />,
|
||||
text: <T id={'payment_made'} />,
|
||||
href: '/payment-mades',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -20,12 +20,14 @@ import { connect } from 'react-redux';
|
||||
|
||||
import AppToaster from 'components/AppToaster';
|
||||
import ErrorMessage from 'components/ErrorMessage';
|
||||
import { ListSelect } from 'components';
|
||||
import { ListSelect, AccountsSelectList } from 'components';
|
||||
|
||||
import Dialog from 'components/Dialog';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withDialogRedux from 'components/DialogReduxConnect';
|
||||
|
||||
import withAccounts from 'containers/Accounts/withAccounts';
|
||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||
import withItemCategoryDetail from 'containers/Items/withItemCategoryDetail';
|
||||
import withItemCategories from 'containers/Items/withItemCategories';
|
||||
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
|
||||
@@ -48,10 +50,16 @@ function ItemCategoryDialog({
|
||||
// #withItemCategories
|
||||
categoriesList,
|
||||
|
||||
//# withAccount
|
||||
accountsList,
|
||||
|
||||
// #withItemCategoriesActions
|
||||
requestSubmitItemCategory,
|
||||
requestFetchItemCategories,
|
||||
requestEditItemCategory,
|
||||
|
||||
// #withAccountsActions
|
||||
requestFetchAccounts,
|
||||
}) {
|
||||
const [selectedParentCategory, setParentCategory] = useState(null);
|
||||
const { formatMessage } = useIntl();
|
||||
@@ -59,12 +67,23 @@ function ItemCategoryDialog({
|
||||
const fetchList = useQuery(['items-categories-list'], () =>
|
||||
requestFetchItemCategories(),
|
||||
);
|
||||
|
||||
const fetchAccounts = useQuery('accounts-list', (key) =>
|
||||
requestFetchAccounts(),
|
||||
);
|
||||
const validationSchema = Yup.object().shape({
|
||||
name: Yup.string()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'category_name_' })),
|
||||
parent_category_id: Yup.string().nullable(),
|
||||
cost_account_id: Yup.number()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'cost_account_' })),
|
||||
sell_account_id: Yup.number()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'sell_account_' })),
|
||||
inventory_account_id: Yup.number()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'inventory_account_' })),
|
||||
description: Yup.string().trim(),
|
||||
});
|
||||
|
||||
@@ -73,6 +92,9 @@ function ItemCategoryDialog({
|
||||
name: '',
|
||||
description: '',
|
||||
parent_category_id: null,
|
||||
cost_account_id: null,
|
||||
sell_account_id: null,
|
||||
inventory_account_id: null,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@@ -95,18 +117,21 @@ function ItemCategoryDialog({
|
||||
},
|
||||
validationSchema,
|
||||
onSubmit: (values, { setSubmitting }) => {
|
||||
const afterSubmit = () => {
|
||||
closeDialog(dialogName);
|
||||
queryCache.invalidateQueries('items-categories-table');
|
||||
queryCache.invalidateQueries('accounts-list');
|
||||
};
|
||||
if (payload.action === 'edit') {
|
||||
requestEditItemCategory(payload.id, values)
|
||||
.then((response) => {
|
||||
closeDialog(dialogName);
|
||||
afterSubmit(response);
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'the_item_category_has_been_successfully_edited',
|
||||
}),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
setSubmitting(false);
|
||||
queryCache.invalidateQueries('items-categories-table');
|
||||
})
|
||||
.catch((error) => {
|
||||
setSubmitting(false);
|
||||
@@ -114,15 +139,13 @@ function ItemCategoryDialog({
|
||||
} else {
|
||||
requestSubmitItemCategory(values)
|
||||
.then((response) => {
|
||||
closeDialog(dialogName);
|
||||
afterSubmit(response);
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'the_item_category_has_been_successfully_created',
|
||||
}),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
setSubmitting(false);
|
||||
queryCache.invalidateQueries('items-categories-table');
|
||||
})
|
||||
.catch((error) => {
|
||||
setSubmitting(false);
|
||||
@@ -130,7 +153,6 @@ function ItemCategoryDialog({
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const filterItemCategory = useCallback(
|
||||
(query, category, _index, exactMatch) => {
|
||||
const normalizedTitle = category.name.toLowerCase();
|
||||
@@ -166,7 +188,8 @@ function ItemCategoryDialog({
|
||||
// Handle the dialog opening.
|
||||
const onDialogOpening = useCallback(() => {
|
||||
fetchList.refetch();
|
||||
}, [fetchList]);
|
||||
fetchAccounts.refetch();
|
||||
}, [fetchList, fetchAccounts]);
|
||||
|
||||
const onChangeParentCategory = useCallback(
|
||||
(parentCategory) => {
|
||||
@@ -176,6 +199,15 @@ function ItemCategoryDialog({
|
||||
[setFieldValue],
|
||||
);
|
||||
|
||||
const onItemAccountSelect = useCallback(
|
||||
(filedName) => {
|
||||
return (account) => {
|
||||
setFieldValue(filedName, account.id);
|
||||
};
|
||||
},
|
||||
[setFieldValue],
|
||||
);
|
||||
|
||||
const onDialogClosed = useCallback(() => {
|
||||
resetForm();
|
||||
closeDialog(dialogName);
|
||||
@@ -273,6 +305,79 @@ function ItemCategoryDialog({
|
||||
{...getFieldProps('description')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{/* cost account */}
|
||||
<FormGroup
|
||||
label={<T id={'cost_account'} />}
|
||||
inline={true}
|
||||
intent={
|
||||
errors.cost_account_id && touched.cost_account_id && Intent.DANGER
|
||||
}
|
||||
helperText={
|
||||
<ErrorMessage {...{ errors, touched }} name="cost_account_id" />
|
||||
}
|
||||
className={classNames(
|
||||
'form-group--cost-account',
|
||||
'form-group--select-list',
|
||||
Classes.FILL,
|
||||
)}
|
||||
>
|
||||
<AccountsSelectList
|
||||
accounts={accountsList}
|
||||
onAccountSelected={onItemAccountSelect('cost_account_id')}
|
||||
defaultSelectText={<T id={'select_account'} />}
|
||||
selectedAccountId={values.cost_account_id}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{/* sell Account */}
|
||||
<FormGroup
|
||||
label={<T id={'sell_account'} />}
|
||||
inline={true}
|
||||
intent={
|
||||
errors.sell_account_id && touched.sell_account_id && Intent.DANGER
|
||||
}
|
||||
helperText={
|
||||
<ErrorMessage {...{ errors, touched }} name="sell_account_id" />
|
||||
}
|
||||
className={classNames(
|
||||
'form-group--sell-account',
|
||||
'form-group--select-list',
|
||||
Classes.FILL,
|
||||
)}
|
||||
>
|
||||
<AccountsSelectList
|
||||
accounts={accountsList}
|
||||
onAccountSelected={onItemAccountSelect('sell_account_id')}
|
||||
defaultSelectText={<T id={'select_account'} />}
|
||||
selectedAccountId={values.sell_account_id}
|
||||
/>
|
||||
</FormGroup>
|
||||
{/* inventory Account */}
|
||||
<FormGroup
|
||||
label={<T id={'inventory_account'} />}
|
||||
inline={true}
|
||||
intent={
|
||||
errors.inventory_account &&
|
||||
touched.inventory_account &&
|
||||
Intent.DANGER
|
||||
}
|
||||
helperText={
|
||||
<ErrorMessage {...{ errors, touched }} name="inventory_account" />
|
||||
}
|
||||
className={classNames(
|
||||
'form-group--sell-account',
|
||||
'form-group--select-list',
|
||||
Classes.FILL,
|
||||
)}
|
||||
>
|
||||
<AccountsSelectList
|
||||
accounts={accountsList}
|
||||
onAccountSelected={onItemAccountSelect('inventory_account')}
|
||||
defaultSelectText={<T id={'select_account'} />}
|
||||
selectedAccountId={values.inventory_account}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
@@ -312,5 +417,9 @@ export default compose(
|
||||
withItemCategories(({ categoriesList }) => ({
|
||||
categoriesList,
|
||||
})),
|
||||
withAccounts(({ accountsList }) => ({
|
||||
accountsList,
|
||||
})),
|
||||
withItemCategoriesActions,
|
||||
withAccountsActions,
|
||||
)(ItemCategoryDialog);
|
||||
|
||||
@@ -14,6 +14,7 @@ import ItemCategoriesDataTable from 'containers/Items/ItemCategoriesTable';
|
||||
import ItemsCategoryActionsBar from 'containers/Items/ItemsCategoryActionsBar';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withResourceActions from 'containers/Resources/withResourcesActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
|
||||
import { compose } from 'utils';
|
||||
@@ -25,10 +26,15 @@ const ItemCategoryList = ({
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
|
||||
// #withViewsActions
|
||||
requestFetchResourceViews,
|
||||
requestFetchResourceFields,
|
||||
|
||||
// #withItemCategoriesActions
|
||||
requestFetchItemCategories,
|
||||
requestDeleteItemCategory,
|
||||
requestDeleteBulkItemCategories,
|
||||
addItemCategoriesTableQueries,
|
||||
|
||||
// #withDialog
|
||||
openDialog,
|
||||
@@ -48,9 +54,13 @@ const ItemCategoryList = ({
|
||||
: changePageTitle(formatMessage({ id: 'category_list' }));
|
||||
}, [id, changePageTitle, formatMessage]);
|
||||
|
||||
const fetchCategories = useQuery(
|
||||
['items-categories-list', filter],
|
||||
(key, query) => requestFetchItemCategories(query),
|
||||
const fetchCategories = useQuery(['items-categories-list'], () =>
|
||||
requestFetchItemCategories(),
|
||||
);
|
||||
|
||||
const fetchResourceFields = useQuery(
|
||||
['resource-fields', 'items_categories'],
|
||||
(key, resourceName) => requestFetchResourceFields(resourceName),
|
||||
);
|
||||
|
||||
const handleFilterChanged = useCallback(() => {}, []);
|
||||
@@ -63,17 +73,23 @@ const ItemCategoryList = ({
|
||||
[setSelectedRows],
|
||||
);
|
||||
|
||||
// Handle fetch data of accounts datatable.
|
||||
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => {
|
||||
setFilter({
|
||||
...(sortBy.length > 0
|
||||
? {
|
||||
column_sort_by: sortBy[0].id,
|
||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
}, []);
|
||||
const handleFetchData = useCallback(
|
||||
({ pageIndex, pageSize, sortBy }) => {
|
||||
const page = pageIndex + 1;
|
||||
|
||||
addItemCategoriesTableQueries({
|
||||
...(sortBy.length > 0
|
||||
? {
|
||||
column_sort_by: sortBy[0].id,
|
||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
||||
}
|
||||
: {}),
|
||||
page_size: pageSize,
|
||||
page,
|
||||
});
|
||||
},
|
||||
[addItemCategoriesTableQueries],
|
||||
);
|
||||
|
||||
const handleDeleteCategory = (itemCategory) => {
|
||||
setDeleteCategory(itemCategory);
|
||||
@@ -139,7 +155,10 @@ const ItemCategoryList = ({
|
||||
]);
|
||||
|
||||
return (
|
||||
<DashboardInsider name={'item-category-list'}>
|
||||
<DashboardInsider
|
||||
loading={fetchResourceFields.isFetching}
|
||||
name={'item-category-list'}
|
||||
>
|
||||
<ItemsCategoryActionsBar
|
||||
selectedRows={selectedRows}
|
||||
onFilterChanged={handleFilterChanged}
|
||||
@@ -151,7 +170,7 @@ const ItemCategoryList = ({
|
||||
onFetchData={handleFetchData}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
onDeleteCategory={handleDeleteCategory}
|
||||
loading={tableLoading}
|
||||
loading={fetchCategories.isFetching}
|
||||
/>
|
||||
|
||||
<Alert
|
||||
@@ -197,4 +216,5 @@ export default compose(
|
||||
withItemCategoriesActions,
|
||||
withDashboardActions,
|
||||
withDialogActions,
|
||||
withResourceActions,
|
||||
)(ItemCategoryList);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 { useFormik, Formik } from 'formik';
|
||||
import {
|
||||
FormGroup,
|
||||
MenuItem,
|
||||
@@ -23,29 +23,28 @@ import ErrorMessage from 'components/ErrorMessage';
|
||||
import Icon from 'components/Icon';
|
||||
import MoneyInputGroup from 'components/MoneyInputGroup';
|
||||
import Dragzone from 'components/Dragzone';
|
||||
import { ListSelect } from 'components';
|
||||
import { ListSelect, AccountsSelectList, If } 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';
|
||||
|
||||
|
||||
const ItemForm = ({
|
||||
// #withItemActions
|
||||
requestSubmitItem,
|
||||
requestEditItem,
|
||||
|
||||
accounts,
|
||||
accountsList,
|
||||
itemDetail,
|
||||
onFormSubmit,
|
||||
onCancelForm,
|
||||
onCancelForm,
|
||||
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
@@ -55,10 +54,10 @@ const ItemForm = ({
|
||||
|
||||
// #withMediaActions
|
||||
requestSubmitMedia,
|
||||
requestDeleteMedia,
|
||||
requestDeleteMedia,
|
||||
}) => {
|
||||
const [payload, setPayload] = useState({});
|
||||
|
||||
|
||||
const history = useHistory();
|
||||
const { formatMessage } = useIntl();
|
||||
const {
|
||||
@@ -72,22 +71,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(),
|
||||
@@ -95,6 +106,8 @@ const ItemForm = ({
|
||||
}),
|
||||
category_id: Yup.number().nullable(),
|
||||
stock: Yup.string() || Yup.boolean(),
|
||||
sellable: Yup.boolean().required(),
|
||||
purchasable: Yup.boolean().required(),
|
||||
});
|
||||
|
||||
const defaultInitialValues = useMemo(
|
||||
@@ -110,27 +123,36 @@ const ItemForm = ({
|
||||
inventory_account_id: null,
|
||||
category_id: null,
|
||||
note: '',
|
||||
sellable: null,
|
||||
purchasable: null,
|
||||
}),
|
||||
[]
|
||||
[],
|
||||
);
|
||||
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,
|
||||
@@ -146,55 +168,56 @@ const ItemForm = ({
|
||||
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
|
||||
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);
|
||||
});
|
||||
setSubmitting(false);
|
||||
saveInvokeSubmit({action:'update',...payload})
|
||||
history.push('/items');
|
||||
resetForm();
|
||||
}).catch((errors)=>{
|
||||
setSubmitting(false)
|
||||
});
|
||||
|
||||
}else{
|
||||
|
||||
} else {
|
||||
requestSubmitItem(formValues).then((response) => {
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'service_has_been_successful_created',
|
||||
}, {
|
||||
name: values.name,
|
||||
service: formatMessage({ id: 'item' }),
|
||||
}),
|
||||
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');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
Promise.all([saveMedia(), deleteMedia()]).then(
|
||||
([savedMediaResponses]) => {
|
||||
const mediaIds = savedMediaResponses.map((res) => res.data.media.id);
|
||||
return saveItem(mediaIds);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -208,53 +231,52 @@ const ItemForm = ({
|
||||
onClick={handleClick}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
|
||||
// Filter Account Items
|
||||
const filterAccounts = (query, account, _index, exactMatch) => {
|
||||
const normalizedTitle = account.name.toLowerCase();
|
||||
const filterAccounts = (query, item, _index, exactMatch) => {
|
||||
const normalizedTitle = item.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
return `${item.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
const onItemAccountSelect = useCallback((filedName) => {
|
||||
return (account) => {
|
||||
setFieldValue(filedName, account.id);
|
||||
};
|
||||
}, [setFieldValue]);
|
||||
const onItemAccountSelect = useCallback(
|
||||
(filedName) => {
|
||||
return (account) => {
|
||||
setFieldValue(filedName, account.id);
|
||||
};
|
||||
},
|
||||
[setFieldValue],
|
||||
);
|
||||
|
||||
const categoryItem = useCallback(
|
||||
(item, { handleClick }) => (
|
||||
<MenuItem text={item.name} onClick={handleClick} />
|
||||
<MenuItem key={item.id} text={item.name} onClick={handleClick} />
|
||||
),
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
const requiredSpan = useMemo(() => <span class='required'>*</span>, []);
|
||||
const infoIcon = useMemo(() => <Icon icon='info-circle' iconSize={12} />, []);
|
||||
const requiredSpan = useMemo(() => <span class="required">*</span>, []);
|
||||
const infoIcon = useMemo(() => <Icon icon="info-circle" iconSize={12} />, []);
|
||||
|
||||
const handleMoneyInputChange = (fieldKey) => (e, value) => {
|
||||
setFieldValue(fieldKey, value);
|
||||
};
|
||||
|
||||
|
||||
const initialAttachmentFiles =useMemo(()=>{
|
||||
const initialAttachmentFiles = useMemo(() => {
|
||||
return itemDetail && itemDetail.media
|
||||
? itemDetail.media.map((attach)=>({
|
||||
|
||||
preview:attach.attachment_file,
|
||||
upload:true,
|
||||
metadata:{...attach}
|
||||
|
||||
})):[];
|
||||
|
||||
},[itemDetail])
|
||||
? itemDetail.media.map((attach) => ({
|
||||
preview: attach.attachment_file,
|
||||
upload: true,
|
||||
metadata: { ...attach },
|
||||
}))
|
||||
: [];
|
||||
}, [itemDetail]);
|
||||
const handleDropFiles = useCallback((_files) => {
|
||||
setFiles(_files.filter((file) => file.uploaded === false));
|
||||
}, []);
|
||||
@@ -267,7 +289,7 @@ const ItemForm = ({
|
||||
}
|
||||
});
|
||||
},
|
||||
[setDeletedFiles, deletedFiles,]
|
||||
[setDeletedFiles, deletedFiles],
|
||||
);
|
||||
|
||||
const handleCancelClickBtn = () => {
|
||||
@@ -275,12 +297,11 @@ const ItemForm = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div class='item-form'>
|
||||
<div class="item-form">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div class='item-form__primary-section'>
|
||||
<div class="item-form__primary-section">
|
||||
<Row>
|
||||
<Col xs={7}>
|
||||
|
||||
{/* Item type */}
|
||||
<FormGroup
|
||||
medium={true}
|
||||
@@ -289,7 +310,7 @@ const ItemForm = ({
|
||||
className={'form-group--item-type'}
|
||||
intent={errors.type && touched.type && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage {...{ errors, touched }} name='type' />
|
||||
<ErrorMessage {...{ errors, touched }} name="type" />
|
||||
}
|
||||
inline={true}
|
||||
>
|
||||
@@ -299,7 +320,7 @@ const ItemForm = ({
|
||||
{...getFieldProps('type')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
|
||||
{/* Item name */}
|
||||
<FormGroup
|
||||
label={<T id={'item_name'} />}
|
||||
@@ -307,7 +328,7 @@ const ItemForm = ({
|
||||
className={'form-group--item-name'}
|
||||
intent={errors.name && touched.name && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage {...{ errors, touched }} name='name' />
|
||||
<ErrorMessage {...{ errors, touched }} name="name" />
|
||||
}
|
||||
inline={true}
|
||||
>
|
||||
@@ -324,7 +345,9 @@ const ItemForm = ({
|
||||
labelInfo={infoIcon}
|
||||
className={'form-group--item-sku'}
|
||||
intent={errors.sku && touched.sku && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...{ errors, touched }} name='sku' />}
|
||||
helperText={
|
||||
<ErrorMessage {...{ errors, touched }} name="sku" />
|
||||
}
|
||||
inline={true}
|
||||
>
|
||||
<InputGroup
|
||||
@@ -343,29 +366,28 @@ const ItemForm = ({
|
||||
errors.category_id && touched.category_id && Intent.DANGER
|
||||
}
|
||||
helperText={
|
||||
<ErrorMessage {...{ errors, touched }} name='category' />
|
||||
<ErrorMessage {...{ errors, touched }} name="category" />
|
||||
}
|
||||
className={classNames(
|
||||
'form-group--select-list',
|
||||
'form-group--category',
|
||||
Classes.FILL
|
||||
Classes.FILL,
|
||||
)}
|
||||
>
|
||||
<ListSelect
|
||||
items={categoriesList}
|
||||
noResults={<MenuItem disabled={true} text="No results." />}
|
||||
itemRenderer={categoryItem}
|
||||
itemPredicate={filterAccounts}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={onItemAccountSelect('category_id')}
|
||||
|
||||
selectedItem={values.category_id}
|
||||
selectedItem={values.customer_id}
|
||||
selectedItemProp={'id'}
|
||||
|
||||
defaultText={<T id={'select_category'} />}
|
||||
labelProp={'name'}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
|
||||
{/* Active checkbox */}
|
||||
<FormGroup
|
||||
label={' '}
|
||||
@@ -374,7 +396,7 @@ const ItemForm = ({
|
||||
>
|
||||
<Checkbox
|
||||
inline={true}
|
||||
label={<T id={'active'}/>}
|
||||
label={<T id={'active'} />}
|
||||
defaultChecked={values.active}
|
||||
{...getFieldProps('active')}
|
||||
/>
|
||||
@@ -395,14 +417,18 @@ const ItemForm = ({
|
||||
|
||||
<Row gutterWidth={16} className={'item-form__accounts-section'}>
|
||||
<Col width={404}>
|
||||
<h4><T id={'purchase_information'}/></h4>
|
||||
<h4>
|
||||
<T id={'sales_information'} />
|
||||
</h4>
|
||||
|
||||
<FormGroup
|
||||
label={<T id={'selling_price'}/>}
|
||||
label={<T id={'selling_price'} />}
|
||||
className={'form-group--item-selling-price'}
|
||||
intent={errors.selling_price && touched.selling_price && Intent.DANGER}
|
||||
intent={
|
||||
errors.selling_price && touched.selling_price && Intent.DANGER
|
||||
}
|
||||
helperText={
|
||||
<ErrorMessage {...{ errors, touched }} name='selling_price' />
|
||||
<ErrorMessage {...{ errors, touched }} name="selling_price" />
|
||||
}
|
||||
inline={true}
|
||||
>
|
||||
@@ -417,9 +443,10 @@ const ItemForm = ({
|
||||
touched.selling_price &&
|
||||
Intent.DANGER,
|
||||
}}
|
||||
disabled={!values.sellable}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
|
||||
{/* Selling account */}
|
||||
<FormGroup
|
||||
label={<T id={'account'} />}
|
||||
@@ -431,32 +458,42 @@ const ItemForm = ({
|
||||
Intent.DANGER
|
||||
}
|
||||
helperText={
|
||||
<ErrorMessage {...{ errors, touched }} name='sell_account_id' />
|
||||
<ErrorMessage {...{ errors, touched }} name="sell_account_id" />
|
||||
}
|
||||
className={classNames(
|
||||
'form-group--sell-account',
|
||||
'form-group--select-list',
|
||||
Classes.FILL
|
||||
Classes.FILL,
|
||||
)}
|
||||
>
|
||||
<ListSelect
|
||||
items={accounts}
|
||||
itemRenderer={accountItem}
|
||||
itemPredicate={filterAccounts}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={onItemAccountSelect('sell_account_id')}
|
||||
<AccountsSelectList
|
||||
accounts={accountsList}
|
||||
onAccountSelected={onItemAccountSelect('sell_account_id')}
|
||||
defaultSelectText={<T id={'select_account'} />}
|
||||
selectedAccountId={values.sell_account_id}
|
||||
disabled={!values.sellable}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
selectedItem={values.sell_account_id}
|
||||
selectedItemProp={'id'}
|
||||
|
||||
defaultText={<T id={'select_account'} />}
|
||||
labelProp={'name'}
|
||||
{/* sellable checkbox */}
|
||||
<FormGroup
|
||||
label={' '}
|
||||
inline={true}
|
||||
className={'form-group--sellable'}
|
||||
>
|
||||
<Checkbox
|
||||
inline={true}
|
||||
label={<T id={'sellable'} />}
|
||||
checked={values.sellable}
|
||||
{...getFieldProps('sellable')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col width={404}>
|
||||
<h4><T id={'sales_information'} /></h4>
|
||||
<h4>
|
||||
<T id={'purchase_information'} />
|
||||
</h4>
|
||||
|
||||
{/* Cost price */}
|
||||
<FormGroup
|
||||
@@ -464,7 +501,7 @@ const ItemForm = ({
|
||||
className={'form-group--item-cost-price'}
|
||||
intent={errors.cost_price && touched.cost_price && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage {...{ errors, touched }} name='cost_price' />
|
||||
<ErrorMessage {...{ errors, touched }} name="cost_price" />
|
||||
}
|
||||
inline={true}
|
||||
>
|
||||
@@ -477,6 +514,7 @@ const ItemForm = ({
|
||||
intent:
|
||||
errors.cost_price && touched.cost_price && Intent.DANGER,
|
||||
}}
|
||||
disabled={!values.purchasable}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -490,25 +528,34 @@ const ItemForm = ({
|
||||
Intent.DANGER
|
||||
}
|
||||
helperText={
|
||||
<ErrorMessage {...{ errors, touched }} name='cost_account_id' />
|
||||
<ErrorMessage {...{ errors, touched }} name="cost_account_id" />
|
||||
}
|
||||
className={classNames(
|
||||
'form-group--cost-account',
|
||||
'form-group--select-list',
|
||||
Classes.FILL
|
||||
Classes.FILL,
|
||||
)}
|
||||
>
|
||||
<ListSelect
|
||||
items={accounts}
|
||||
itemRenderer={accountItem}
|
||||
itemPredicate={filterAccounts}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={onItemAccountSelect('cost_account_id')}
|
||||
<AccountsSelectList
|
||||
accounts={accountsList}
|
||||
onAccountSelected={onItemAccountSelect('cost_account_id')}
|
||||
defaultSelectText={<T id={'select_account'} />}
|
||||
selectedAccountId={values.cost_account_id}
|
||||
disabled={!values.purchasable}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
defaultText={<T id={'select_account'} />}
|
||||
labelProp={'name'}
|
||||
selectedItem={values.cost_account_id}
|
||||
selectedItemProp={'id'}
|
||||
{/* purchasable checkbox */}
|
||||
<FormGroup
|
||||
label={' '}
|
||||
inline={true}
|
||||
className={'form-group--purchasable'}
|
||||
>
|
||||
<Checkbox
|
||||
inline={true}
|
||||
label={<T id={'purchasable'} />}
|
||||
defaultChecked={values.purchasable}
|
||||
{...getFieldProps('purchasable')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
@@ -521,35 +568,35 @@ const ItemForm = ({
|
||||
</h4>
|
||||
|
||||
<FormGroup
|
||||
label={<T id={'inventory_account'}/>}
|
||||
label={<T id={'inventory_account'} />}
|
||||
inline={true}
|
||||
intent={
|
||||
errors.inventory_account_id &&
|
||||
touched.inventory_account_id &&
|
||||
Intent.DANGER
|
||||
}
|
||||
helperText={<ErrorMessage {...{ errors, touched }} name='inventory_account_id' />}
|
||||
helperText={
|
||||
<ErrorMessage
|
||||
{...{ errors, touched }}
|
||||
name="inventory_account_id"
|
||||
/>
|
||||
}
|
||||
className={classNames(
|
||||
'form-group--item-inventory_account',
|
||||
'form-group--select-list',
|
||||
Classes.FILL
|
||||
Classes.FILL,
|
||||
)}
|
||||
>
|
||||
<ListSelect
|
||||
items={accounts}
|
||||
itemRenderer={accountItem}
|
||||
itemPredicate={filterAccounts}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={onItemAccountSelect('inventory_account_id')}
|
||||
|
||||
defaultText={<T id={'select_account'} />}
|
||||
labelProp={'name'}
|
||||
selectedItem={values.inventory_account_id}
|
||||
selectedItemProp={'id'} />
|
||||
<AccountsSelectList
|
||||
accounts={accountsList}
|
||||
onAccountSelected={onItemAccountSelect('inventory_account_id')}
|
||||
defaultSelectText={<T id={'select_account'} />}
|
||||
selectedAccountId={values.inventory_account_id}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={<T id={'opening_stock'}/>}
|
||||
label={<T id={'opening_stock'} />}
|
||||
className={'form-group--item-stock'}
|
||||
inline={true}
|
||||
>
|
||||
@@ -562,14 +609,17 @@ const ItemForm = ({
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div class='form__floating-footer'>
|
||||
<Button intent={Intent.PRIMARY} disabled={isSubmitting} type='submit'>
|
||||
|
||||
{ itemDetail && itemDetail.id ? <T id={'edit'}/> : <T id={'save'}/> }
|
||||
<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'}/>
|
||||
<T id={'save_as_draft'} />
|
||||
</Button>
|
||||
|
||||
<Button className={'ml1'} onClick={handleCancelClickBtn}>
|
||||
@@ -582,8 +632,8 @@ const ItemForm = ({
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withAccounts(({accounts})=>({
|
||||
accounts,
|
||||
withAccounts(({ accountsList }) => ({
|
||||
accountsList,
|
||||
})),
|
||||
withAccountDetail,
|
||||
withItemsActions,
|
||||
|
||||
@@ -23,9 +23,10 @@ import { If } from 'components';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
||||
import withItems from 'containers/Items/withItems';
|
||||
import withItemsActions from './withItemsActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const ItemsActionsBar = ({
|
||||
openDialog,
|
||||
@@ -36,6 +37,9 @@ const ItemsActionsBar = ({
|
||||
// #withItems
|
||||
itemsViews,
|
||||
|
||||
//#withItemActions
|
||||
addItemsTableQueries,
|
||||
|
||||
onFilterChanged,
|
||||
selectedRows = [],
|
||||
onBulkDelete,
|
||||
@@ -56,10 +60,26 @@ const ItemsActionsBar = ({
|
||||
selectedRows,
|
||||
]);
|
||||
|
||||
// name
|
||||
// const filterDropdown = FilterDropdown({
|
||||
// fields: resourceFields,
|
||||
// onFilterChange: (filterConditions) => {
|
||||
// setFilterCount(filterConditions.length);
|
||||
// onFilterChanged && onFilterChanged(filterConditions);
|
||||
// },
|
||||
// });
|
||||
|
||||
const filterDropdown = FilterDropdown({
|
||||
initialCondition: {
|
||||
fieldKey: 'name',
|
||||
compatator: 'contains',
|
||||
value: '',
|
||||
},
|
||||
fields: resourceFields,
|
||||
onFilterChange: (filterConditions) => {
|
||||
setFilterCount(filterConditions.length);
|
||||
addItemsTableQueries({
|
||||
filter_roles: filterConditions || '',
|
||||
});
|
||||
onFilterChanged && onFilterChanged(filterConditions);
|
||||
},
|
||||
});
|
||||
@@ -107,9 +127,11 @@ const ItemsActionsBar = ({
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||
text={
|
||||
filterCount <= 0 ?
|
||||
(<T id={'filter'} />) :
|
||||
(`${filterCount} ${formatMessage({ id: 'filters_applied' })}`)
|
||||
filterCount <= 0 ? (
|
||||
<T id={'filter'} />
|
||||
) : (
|
||||
`${filterCount} ${formatMessage({ id: 'filters_applied' })}`
|
||||
)
|
||||
}
|
||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||
/>
|
||||
@@ -140,7 +162,14 @@ const ItemsActionsBar = ({
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
resourceName: 'items',
|
||||
});
|
||||
|
||||
const withItemsActionsBar = connect(mapStateToProps);
|
||||
|
||||
export default compose(
|
||||
withItemsActionsBar,
|
||||
withDialogActions,
|
||||
withItems(({ itemsViews }) => ({
|
||||
itemsViews,
|
||||
@@ -148,4 +177,5 @@ export default compose(
|
||||
withResourceDetail(({ resourceFields }) => ({
|
||||
resourceFields,
|
||||
})),
|
||||
withItemsActions,
|
||||
)(ItemsActionsBar);
|
||||
|
||||
@@ -12,16 +12,18 @@ import {
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import { If, Icon } from 'components';
|
||||
|
||||
import Icon from 'components/Icon';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import FilterDropdown from 'components/FilterDropdown';
|
||||
import { If } from 'components';
|
||||
|
||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
|
||||
import withItemCategories from './withItemCategories';
|
||||
import withItemCategoriesActions from './withItemCategoriesActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const ItemsCategoryActionsBar = ({
|
||||
@@ -31,6 +33,12 @@ const ItemsCategoryActionsBar = ({
|
||||
// #withDialog
|
||||
openDialog,
|
||||
|
||||
// #withItemCategories
|
||||
categoriesViews,
|
||||
|
||||
// #withItemCategoriesActions
|
||||
addItemCategoriesTableQueries,
|
||||
|
||||
// #ownProps
|
||||
selectedRows = [],
|
||||
onFilterChanged,
|
||||
@@ -46,17 +54,21 @@ const ItemsCategoryActionsBar = ({
|
||||
selectedRows,
|
||||
]);
|
||||
|
||||
// const handleDeleteCategory = useCallback((category) => {
|
||||
// onDeleteCategory(selectedRows);
|
||||
// }, [selectedRows, onDeleteCategory]);
|
||||
|
||||
const filterDropdown = FilterDropdown({
|
||||
fields: resourceFields,
|
||||
onFilterChange: (filterConditions) => {
|
||||
setFilterCount(filterConditions.length || 0);
|
||||
onFilterChanged && onFilterChanged(filterConditions);
|
||||
},
|
||||
});
|
||||
// const filterDropdown = FilterDropdown({
|
||||
// fields: resourceFields,
|
||||
// initialCondition: {
|
||||
// fieldKey: 'name',
|
||||
// compatator: 'contains',
|
||||
// value: '',
|
||||
// },
|
||||
// onFilterChange: (filterConditions) => {
|
||||
// setFilterCount(filterConditions.length || 0);
|
||||
// addItemCategoriesTableQueries({
|
||||
// filter_roles: filterConditions || '',
|
||||
// });
|
||||
// onFilterChanged && onFilterChanged(filterConditions);
|
||||
// },
|
||||
// });
|
||||
|
||||
const handelBulkDelete = useCallback(() => {
|
||||
onBulkDelete && onBulkDelete(selectedRows.map((r) => r.id));
|
||||
@@ -75,22 +87,29 @@ const ItemsCategoryActionsBar = ({
|
||||
|
||||
<Popover
|
||||
minimal={true}
|
||||
content={filterDropdown}
|
||||
// content={filterDropdown}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
canOutsideClickClose={true}
|
||||
>
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||
text={ filterCount <= 0 ? <T id={'filter'}/> : `${filterCount} filters applied`}
|
||||
icon={<Icon icon='filter-16' iconSize={16} />}
|
||||
text={
|
||||
filterCount <= 0 ? (
|
||||
<T id={'filter'} />
|
||||
) : (
|
||||
`${filterCount} filters applied`
|
||||
)
|
||||
}
|
||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
<If condition={hasSelectedRows}>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='trash-16' iconSize={16} />}
|
||||
text={<T id={'delete'}/>}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handelBulkDelete}
|
||||
/>
|
||||
@@ -98,13 +117,13 @@ const ItemsCategoryActionsBar = ({
|
||||
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='file-import-16' iconSize={16} />}
|
||||
text={<T id={'import'}/>}
|
||||
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||
text={<T id={'import'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='file-export-16' iconSize={16} />}
|
||||
text={<T id={'export'}/>}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
@@ -114,7 +133,6 @@ const ItemsCategoryActionsBar = ({
|
||||
const mapStateToProps = (state, props) => ({
|
||||
resourceName: 'items_categories',
|
||||
});
|
||||
|
||||
const withItemsCategoriesActionsBar = connect(mapStateToProps);
|
||||
|
||||
export default compose(
|
||||
@@ -124,4 +142,8 @@ export default compose(
|
||||
withResourceDetail(({ resourceFields }) => ({
|
||||
resourceFields,
|
||||
})),
|
||||
// withItemCategories(({ categoriesViews }) => ({
|
||||
// categoriesViews,
|
||||
// })),
|
||||
withItemCategoriesActions,
|
||||
)(ItemsCategoryActionsBar);
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import React, { useEffect, useCallback, useState,useMemo } from 'react';
|
||||
import {
|
||||
Route,
|
||||
Switch,
|
||||
useHistory
|
||||
} from 'react-router-dom';
|
||||
import {
|
||||
Intent,
|
||||
Alert,
|
||||
} from '@blueprintjs/core';
|
||||
import React, { useEffect, useCallback, useState, useMemo } from 'react';
|
||||
import { Route, Switch, useHistory } from 'react-router-dom';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
import { useQuery } from 'react-query';
|
||||
import { FormattedMessage as T, FormattedHTMLMessage, useIntl } from 'react-intl';
|
||||
import {
|
||||
FormattedMessage as T,
|
||||
FormattedHTMLMessage,
|
||||
useIntl,
|
||||
} from 'react-intl';
|
||||
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import ItemsActionsBar from 'containers/Items/ItemsActionsBar';
|
||||
import { compose } from 'utils';
|
||||
|
||||
import ItemsDataTable from './ItemsDataTable';
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
|
||||
import ItemsViewsTabs from 'containers/Items/ItemsViewsTabs';
|
||||
import ItemsDataTable from './ItemsDataTable';
|
||||
import ItemsActionsBar from 'containers/Items/ItemsActionsBar';
|
||||
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
import AppToaster from 'components/AppToaster';
|
||||
|
||||
import withItems from 'containers/Items/withItems';
|
||||
@@ -27,7 +24,6 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withItemsActions from 'containers/Items/withItemsActions';
|
||||
import withViewsActions from 'containers/Views/withViewsActions';
|
||||
|
||||
|
||||
function ItemsList({
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
@@ -51,37 +47,41 @@ function ItemsList({
|
||||
const [bulkDelete, setBulkDelete] = useState(false);
|
||||
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
changePageTitle(formatMessage({id:'items_list'}));
|
||||
changePageTitle(formatMessage({ id: 'items_list' }));
|
||||
}, [changePageTitle]);
|
||||
|
||||
const fetchHook = useQuery('items-resource', () => {
|
||||
return Promise.all([
|
||||
requestFetchResourceViews('items'),
|
||||
requestFetchResourceFields('items'),
|
||||
]);
|
||||
});
|
||||
const fetchResourceViews = useQuery(
|
||||
['resource-views', 'items'],
|
||||
(key, resourceName) => requestFetchResourceViews(resourceName),
|
||||
);
|
||||
|
||||
const fetchItems = useQuery(['items-table', itemsTableQuery],
|
||||
() => requestFetchItems({}));
|
||||
const fetchResourceFields = useQuery(
|
||||
['resource-fields', 'items'],
|
||||
(key, resourceName) => requestFetchResourceFields(resourceName),
|
||||
);
|
||||
|
||||
const fetchItems = useQuery(['items-table', itemsTableQuery], () =>
|
||||
requestFetchItems({}),
|
||||
);
|
||||
|
||||
// Handle click delete item.
|
||||
const handleDeleteItem = useCallback((item) => {
|
||||
setDeleteItem(item);
|
||||
}, [setDeleteItem]);
|
||||
const handleDeleteItem = useCallback(
|
||||
(item) => {
|
||||
setDeleteItem(item);
|
||||
},
|
||||
[setDeleteItem],
|
||||
);
|
||||
|
||||
const handleEditItem = useCallback(
|
||||
(item) => {
|
||||
history.push(`/items/${item.id}/edit`);
|
||||
},
|
||||
[history]
|
||||
[history],
|
||||
);
|
||||
|
||||
|
||||
|
||||
// Handle cancel delete the item.
|
||||
const handleCancelDeleteItem = useCallback(() => {
|
||||
setDeleteItem(false);
|
||||
@@ -98,29 +98,43 @@ function ItemsList({
|
||||
});
|
||||
setDeleteItem(false);
|
||||
});
|
||||
}, [requestDeleteItem, deleteItem,formatMessage]);
|
||||
}, [requestDeleteItem, deleteItem, formatMessage]);
|
||||
|
||||
// Handle fetch data table.
|
||||
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => {
|
||||
addItemsTableQueries({
|
||||
...(sortBy.length > 0) ? {
|
||||
column_sort_order: sortBy[0].id,
|
||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
||||
} : {},
|
||||
});
|
||||
}, [fetchItems, addItemsTableQueries]);
|
||||
const handleFetchData = useCallback(
|
||||
({ pageIndex, pageSize, sortBy }) => {
|
||||
const page = pageIndex + 1;
|
||||
|
||||
addItemsTableQueries({
|
||||
...(sortBy.length > 0
|
||||
? {
|
||||
column_sort_by: sortBy[0].id,
|
||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
||||
}
|
||||
: {}),
|
||||
page_size: pageSize,
|
||||
page,
|
||||
});
|
||||
},
|
||||
[addItemsTableQueries],
|
||||
);
|
||||
|
||||
// Handle filter change to re-fetch the items.
|
||||
const handleFilterChanged = useCallback((filterConditions) => {
|
||||
addItemsTableQueries({
|
||||
filter_roles: filterConditions || '',
|
||||
});
|
||||
}, [fetchItems]);
|
||||
const handleFilterChanged = useCallback(
|
||||
(filterConditions) => {
|
||||
addItemsTableQueries({
|
||||
filter_roles: filterConditions || '',
|
||||
});
|
||||
},
|
||||
[fetchItems],
|
||||
);
|
||||
|
||||
// Handle custom view change to re-fetch the items.
|
||||
const handleCustomViewChanged = useCallback((customViewId) => {
|
||||
setTableLoading(true);
|
||||
}, [fetchItems]);
|
||||
const handleCustomViewChanged = useCallback(
|
||||
(customViewId) => {
|
||||
setTableLoading(true);
|
||||
},
|
||||
[fetchItems],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (tableLoading && !fetchItems.isFetching) {
|
||||
@@ -129,108 +143,124 @@ function ItemsList({
|
||||
}, [tableLoading, fetchItems.isFetching]);
|
||||
|
||||
// Handle selected rows change.
|
||||
const handleSelectedRowsChange = useCallback((accounts) => {
|
||||
setSelectedRows(accounts);
|
||||
}, [setSelectedRows]);
|
||||
|
||||
// Calculates the data table selected rows count.
|
||||
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [selectedRows]);
|
||||
const handleSelectedRowsChange = useCallback(
|
||||
(accounts) => {
|
||||
setSelectedRows(accounts);
|
||||
},
|
||||
[setSelectedRows],
|
||||
);
|
||||
|
||||
|
||||
// Calculates the data table selected rows count.
|
||||
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
|
||||
selectedRows,
|
||||
]);
|
||||
|
||||
// Handle items bulk delete button click.,
|
||||
|
||||
const handleBulkDelete = useCallback((itemsIds) => {
|
||||
setBulkDelete(itemsIds);
|
||||
}, [setBulkDelete]);
|
||||
const handleBulkDelete = useCallback(
|
||||
(itemsIds) => {
|
||||
setBulkDelete(itemsIds);
|
||||
},
|
||||
[setBulkDelete],
|
||||
);
|
||||
|
||||
// Handle confirm items bulk delete.
|
||||
const handleConfirmBulkDelete = useCallback(() => {
|
||||
requestDeleteBulkItems(bulkDelete).then(() => {
|
||||
setBulkDelete(false);
|
||||
AppToaster.show({
|
||||
message: formatMessage({ id: 'the_items_has_been_successfully_deleted' }),
|
||||
intent: Intent.SUCCESS,
|
||||
// Handle confirm items bulk delete.
|
||||
const handleConfirmBulkDelete = useCallback(() => {
|
||||
requestDeleteBulkItems(bulkDelete)
|
||||
.then(() => {
|
||||
setBulkDelete(false);
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'the_items_has_been_successfully_deleted',
|
||||
}),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch((errors) => {
|
||||
setBulkDelete(false);
|
||||
});
|
||||
}).catch((errors) => {
|
||||
setBulkDelete(false);
|
||||
});
|
||||
}, [requestDeleteBulkItems, bulkDelete,formatMessage]);
|
||||
|
||||
// Handle cancel accounts bulk delete.
|
||||
const handleCancelBulkDelete = useCallback(() => {
|
||||
setBulkDelete(false);
|
||||
}, []);
|
||||
}, [requestDeleteBulkItems, bulkDelete, formatMessage]);
|
||||
|
||||
// Handle cancel accounts bulk delete.
|
||||
const handleCancelBulkDelete = useCallback(() => {
|
||||
setBulkDelete(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DashboardInsider
|
||||
isLoading={fetchHook.isFetching}
|
||||
name={'items-list'}>
|
||||
loading={fetchResourceViews.isFetching || fetchResourceFields.isFetching}
|
||||
// isLoading={fetchHook.isFetching}
|
||||
|
||||
name={'items-list'}
|
||||
>
|
||||
<ItemsActionsBar
|
||||
selectedRows={selectedRows}
|
||||
selectedRows={selectedRows}
|
||||
onFilterChanged={handleFilterChanged}
|
||||
onBulkDelete={handleBulkDelete}/>
|
||||
onBulkDelete={handleBulkDelete}
|
||||
/>
|
||||
|
||||
<DashboardPageContent>
|
||||
<Switch>
|
||||
<Route
|
||||
exact={true}
|
||||
path={[
|
||||
'/items/:custom_view_id/custom_view',
|
||||
'/items'
|
||||
]}>
|
||||
<ItemsViewsTabs
|
||||
onViewChanged={handleCustomViewChanged} />
|
||||
|
||||
path={['/items/:custom_view_id/custom_view', '/items']}
|
||||
>
|
||||
<ItemsViewsTabs onViewChanged={handleCustomViewChanged} />
|
||||
|
||||
<ItemsDataTable
|
||||
loading={tableLoading}
|
||||
loading={fetchItems.isFetching}
|
||||
onDeleteItem={handleDeleteItem}
|
||||
onEditItem={handleEditItem}
|
||||
onFetchData={handleFetchData}
|
||||
onSelectedRowsChange={handleSelectedRowsChange} />
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
/>
|
||||
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'}/>}
|
||||
confirmButtonText={<T id={'delete'}/>}
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={<T id={'delete'} />}
|
||||
icon="trash"
|
||||
intent={Intent.DANGER}
|
||||
isOpen={deleteItem}
|
||||
onCancel={handleCancelDeleteItem}
|
||||
onConfirm={handleConfirmDeleteItem}>
|
||||
onConfirm={handleConfirmDeleteItem}
|
||||
>
|
||||
<p>
|
||||
<FormattedHTMLMessage
|
||||
id={'once_delete_this_item_you_will_able_to_restore_it'} />
|
||||
id={'once_delete_this_item_you_will_able_to_restore_it'}
|
||||
/>
|
||||
</p>
|
||||
</Alert>
|
||||
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'}/>}
|
||||
confirmButtonText={`${formatMessage({id:'delete'})} (${selectedRowsCount})`}
|
||||
icon="trash"
|
||||
intent={Intent.DANGER}
|
||||
isOpen={bulkDelete}
|
||||
onCancel={handleCancelBulkDelete}
|
||||
onConfirm={handleConfirmBulkDelete}
|
||||
>
|
||||
<p>
|
||||
<T id={'once_delete_these_items_you_will_not_able_restore_them'} />
|
||||
</p>
|
||||
</Alert>
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={`${formatMessage({
|
||||
id: 'delete',
|
||||
})} (${selectedRowsCount})`}
|
||||
icon="trash"
|
||||
intent={Intent.DANGER}
|
||||
isOpen={bulkDelete}
|
||||
onCancel={handleCancelBulkDelete}
|
||||
onConfirm={handleConfirmBulkDelete}
|
||||
>
|
||||
<p>
|
||||
<T
|
||||
id={'once_delete_these_items_you_will_not_able_restore_them'}
|
||||
/>
|
||||
</p>
|
||||
</Alert>
|
||||
</Route>
|
||||
</Switch>
|
||||
</DashboardPageContent>
|
||||
</DashboardInsider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withItems(({ itemsTableQuery }) => ({
|
||||
itemsTableQuery,
|
||||
})),
|
||||
withResourceActions,
|
||||
withDashboardActions,
|
||||
withItemsActions,
|
||||
withViewsActions,
|
||||
withItems(({ itemsTableQuery }) => ({
|
||||
itemsTableQuery,
|
||||
})),
|
||||
)(ItemsList);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
@@ -7,20 +7,22 @@ import {
|
||||
NavbarGroup,
|
||||
Tabs,
|
||||
Tab,
|
||||
Button
|
||||
Button,
|
||||
} from '@blueprintjs/core';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Icon from 'components/Icon';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import { compose } from 'utils';
|
||||
import {useUpdateEffect} from 'hooks';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
import { DashboardViewsTabs } from 'components';
|
||||
import { pick, debounce } from 'lodash';
|
||||
|
||||
import withItemsActions from 'containers/Items/withItemsActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withViewDetail from 'containers/Views/withViewDetails';
|
||||
import withItems from 'containers/Items/withItems';
|
||||
|
||||
import { FormattedMessage as T} from 'react-intl';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
|
||||
function ItemsViewsTabs({
|
||||
// #withViewDetail
|
||||
@@ -42,7 +44,7 @@ function ItemsViewsTabs({
|
||||
onViewChanged,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { custom_view_id: customViewId } = useParams();
|
||||
const { custom_view_id: customViewId = null } = useParams();
|
||||
|
||||
const handleClickNewView = () => {
|
||||
setTopbarEditView(null);
|
||||
@@ -51,12 +53,12 @@ function ItemsViewsTabs({
|
||||
|
||||
const handleViewLinkClick = () => {
|
||||
setTopbarEditView(customViewId);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
changeItemsCurrentView(customViewId || -1);
|
||||
setTopbarEditView(customViewId);
|
||||
changePageSubtitle((customViewId && viewItem) ? viewItem.name : '');
|
||||
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
|
||||
|
||||
addItemsTableQueries({
|
||||
custom_view_id: customViewId || null,
|
||||
@@ -67,45 +69,37 @@ function ItemsViewsTabs({
|
||||
changeItemsCurrentView(-1);
|
||||
changePageSubtitle('');
|
||||
};
|
||||
}, [customViewId]);
|
||||
}, [customViewId, addItemsTableQueries, changeItemsCurrentView]);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
onViewChanged && onViewChanged(customViewId);
|
||||
}, [customViewId]);
|
||||
|
||||
const tabs = itemsViews.map(view => {
|
||||
const baseUrl = '/items';
|
||||
const link = (
|
||||
<Link to={`${baseUrl}/${view.id}/custom_view`} onClick={handleViewLinkClick}>
|
||||
{view.name}
|
||||
</Link>
|
||||
);
|
||||
return (<Tab id={`custom_view_${view.id}`} title={link} />);
|
||||
});
|
||||
const debounceChangeHistory = useRef(
|
||||
debounce((toUrl) => {
|
||||
history.push(toUrl);
|
||||
}, 250),
|
||||
);
|
||||
|
||||
const handleTabsChange = (viewId) => {
|
||||
const toPath = viewId ? `${viewId}/custom_view` : '';
|
||||
debounceChangeHistory.current(`/items/${toPath}`);
|
||||
setTopbarEditView(viewId);
|
||||
};
|
||||
|
||||
const tabs = itemsViews.map((view) => ({
|
||||
...pick(view, ['name', 'id']),
|
||||
}));
|
||||
return (
|
||||
<Navbar className='navbar--dashboard-views'>
|
||||
<Navbar className="navbar--dashboard-views">
|
||||
<NavbarGroup align={Alignment.LEFT}>
|
||||
<Tabs
|
||||
id='navbar'
|
||||
large={true}
|
||||
selectedTabId={customViewId ? `custom_view_${customViewId}` : 'all'}
|
||||
className='tabs--dashboard-views'
|
||||
>
|
||||
<Tab
|
||||
id='all'
|
||||
title={<Link to={`/items`}><T id={'all'}/></Link>}
|
||||
onClick={handleViewLinkClick} />
|
||||
|
||||
{tabs}
|
||||
|
||||
<Button
|
||||
className='button--new-view'
|
||||
icon={<Icon icon='plus' />}
|
||||
onClick={handleClickNewView}
|
||||
minimal={true}
|
||||
/>
|
||||
</Tabs>
|
||||
<DashboardViewsTabs
|
||||
initialViewId={customViewId}
|
||||
baseUrl={'/items'}
|
||||
tabs={tabs}
|
||||
onNewViewTabClick={handleClickNewView}
|
||||
onChange={handleTabsChange}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</Navbar>
|
||||
);
|
||||
@@ -123,8 +117,8 @@ export default compose(
|
||||
withItemsViewsTabs,
|
||||
withDashboardActions,
|
||||
withItemsActions,
|
||||
withViewDetail,
|
||||
withItems( ({ itemsViews }) => ({
|
||||
withViewDetail(),
|
||||
withItems(({ itemsViews }) => ({
|
||||
itemsViews,
|
||||
}))
|
||||
})),
|
||||
)(ItemsViewsTabs);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
getItemsCategoriesListFactory
|
||||
} from 'store/itemCategories/ItemsCategories.selectors';
|
||||
|
||||
import { getItemsCategoriesListFactory } from 'store/itemCategories/ItemsCategories.selectors';
|
||||
import { getResourceViews } from 'store/customViews/customViews.selectors';
|
||||
|
||||
export default (mapState) => {
|
||||
const getItemsCategoriesList = getItemsCategoriesListFactory();
|
||||
@@ -10,10 +8,11 @@ export default (mapState) => {
|
||||
const mapStateToProps = (state, props) => {
|
||||
const mapped = {
|
||||
categoriesList: getItemsCategoriesList(state, props),
|
||||
itemCategoriesViews: getResourceViews(state, props, 'items_categories'),
|
||||
categoriesTableLoading: state.itemCategories.loading,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapState;
|
||||
};
|
||||
|
||||
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,16 +4,30 @@ import {
|
||||
submitItemCategory,
|
||||
deleteItemCategory,
|
||||
editItemCategory,
|
||||
deleteBulkItemCategories
|
||||
|
||||
deleteBulkItemCategories,
|
||||
} from 'store/itemCategories/itemsCategory.actions';
|
||||
import t from 'store/types';
|
||||
|
||||
export const mapDispatchToProps = (dispatch) => ({
|
||||
requestSubmitItemCategory: (form) => dispatch(submitItemCategory({ form })),
|
||||
requestFetchItemCategories: (query) => dispatch(fetchItemCategories({ query })),
|
||||
requestFetchItemCategories: (query) =>
|
||||
dispatch(fetchItemCategories({ query })),
|
||||
requestDeleteItemCategory: (id) => dispatch(deleteItemCategory(id)),
|
||||
requestEditItemCategory: (id, form) => dispatch(editItemCategory(id, form)),
|
||||
requestDeleteBulkItemCategories:(ids)=>dispatch(deleteBulkItemCategories({ids}))
|
||||
requestDeleteBulkItemCategories: (ids) =>
|
||||
dispatch(deleteBulkItemCategories({ ids })),
|
||||
|
||||
changeItemCategoriesView: (id) =>
|
||||
dispatch({
|
||||
type: t.ITEM_CATEGORIES_SET_CURRENT_VIEW,
|
||||
currentViewId: parseInt(id, 10),
|
||||
}),
|
||||
|
||||
addItemCategoriesTableQueries: (queries) =>
|
||||
dispatch({
|
||||
type: t.ITEM_CATEGORIES_TABLE_QUERIES_ADD,
|
||||
queries,
|
||||
}),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps);
|
||||
export default connect(null, mapDispatchToProps);
|
||||
|
||||
@@ -9,9 +9,10 @@ import * as Yup from 'yup';
|
||||
import { useFormik } from 'formik';
|
||||
import moment from 'moment';
|
||||
import { Intent, FormGroup, TextArea } from '@blueprintjs/core';
|
||||
import { Row, Col } from 'react-grid-system';
|
||||
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import { pick, omit } from 'lodash';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
import BillFormHeader from './BillFormHeader';
|
||||
import EstimatesItemsTable from 'containers/Sales/Estimate/EntriesItemsTable';
|
||||
@@ -82,6 +83,7 @@ function BillForm({
|
||||
}
|
||||
}, [changePageTitle, bill, formatMessage]);
|
||||
|
||||
// @todo abstruct validation schema to sperated file.
|
||||
const validationSchema = Yup.object().shape({
|
||||
vendor_id: Yup.number()
|
||||
.required()
|
||||
@@ -102,7 +104,6 @@ function BillForm({
|
||||
.min(1)
|
||||
.max(1024)
|
||||
.label(formatMessage({ id: 'note' })),
|
||||
|
||||
entries: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
quantity: Yup.number().nullable(),
|
||||
@@ -180,14 +181,6 @@ function BillForm({
|
||||
[bill, defaultInitialValues, defaultBill],
|
||||
);
|
||||
|
||||
// const initialValues = useMemo(
|
||||
// () => ({
|
||||
// ...defaultInitialValues,
|
||||
// entries: orderingIndex(defaultInitialValues.entries),
|
||||
// }),
|
||||
// [defaultInitialValues],
|
||||
// );
|
||||
|
||||
const initialAttachmentFiles = useMemo(() => {
|
||||
return bill && bill.media
|
||||
? bill.media.map((attach) => ({
|
||||
@@ -207,14 +200,10 @@ function BillForm({
|
||||
onSubmit: async (values, { setSubmitting, setErrors, resetForm }) => {
|
||||
setSubmitting(true);
|
||||
|
||||
const entries = values.entries.filter(
|
||||
(item) => item.item_id && item.quantity,
|
||||
);
|
||||
const form = {
|
||||
...values,
|
||||
entries,
|
||||
entries: values.entries.filter((item) => item.item_id && item.quantity),
|
||||
};
|
||||
|
||||
const requestForm = { ...form };
|
||||
if (bill && bill.id) {
|
||||
requestEditBill(bill.id, requestForm)
|
||||
@@ -243,8 +232,8 @@ function BillForm({
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
setSubmitting(false);
|
||||
resetForm();
|
||||
saveBillSubmit({ action: 'new', ...payload });
|
||||
resetForm();
|
||||
})
|
||||
.catch((errors) => {
|
||||
setSubmitting(false);
|
||||
@@ -292,10 +281,6 @@ function BillForm({
|
||||
orderingIndex([...formik.values.entries, defaultBill]),
|
||||
);
|
||||
};
|
||||
|
||||
console.log(formik.errors, 'Errors');
|
||||
console.log(formik.values, 'values');
|
||||
|
||||
return (
|
||||
<div className={'bill-form'}>
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
@@ -306,15 +291,25 @@ function BillForm({
|
||||
onClickAddNewRow={onClickAddNewRow}
|
||||
onClickClearAllLines={onClickCleanAllLines}
|
||||
/>
|
||||
<FormGroup label={<T id={'note'} />} className={'form-group--'}>
|
||||
<TextArea growVertically={true} {...formik.getFieldProps('note')} />
|
||||
</FormGroup>
|
||||
<Dragzone
|
||||
initialFiles={initialAttachmentFiles}
|
||||
onDrop={handleDropFiles}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
hint={'Attachments: Maxiumum size: 20MB'}
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
<FormGroup label={<T id={'note'} />} className={'form-group--'}>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('note')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<Dragzone
|
||||
initialFiles={initialAttachmentFiles}
|
||||
onDrop={handleDropFiles}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
hint={'Attachments: Maxiumum size: 20MB'}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</form>
|
||||
<BillFormFooter
|
||||
formik={formik}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function BillFormFooter({
|
||||
bill,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<div className={'form__floating-footer'}>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
|
||||
@@ -76,12 +76,10 @@ function BillFormHeader({
|
||||
}
|
||||
};
|
||||
|
||||
console.log(vendorsCurrentPage, 'vendorsCurrentPage');
|
||||
console.log(vendorItems, 'vendorItems');
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{/* vendor account name */}
|
||||
<div className="page-form page-form--bill">
|
||||
<div className={'page-form__primary-section'}>
|
||||
{/* Vendor account name */}
|
||||
<FormGroup
|
||||
label={<T id={'vendor_name'} />}
|
||||
inline={true}
|
||||
|
||||
@@ -106,6 +106,7 @@ function BillsDataTable({
|
||||
text={formatMessage({ id: 'delete_bill' })}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteBill(bill)}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
/>
|
||||
</Menu>
|
||||
),
|
||||
|
||||
@@ -3,20 +3,25 @@ import { getResourceViews } from 'store/customViews/customViews.selectors';
|
||||
import {
|
||||
getBillCurrentPageFactory,
|
||||
getBillPaginationMetaFactory,
|
||||
getBillTableQuery,
|
||||
getBillTableQueryFactory,
|
||||
} from 'store/Bills/bills.selectors';
|
||||
|
||||
export default (mapState) => {
|
||||
const getBillsItems = getBillCurrentPageFactory();
|
||||
const getBillsPaginationMeta = getBillPaginationMetaFactory();
|
||||
const getBillTableQuery = getBillTableQueryFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const query = getBillTableQuery(state, props);
|
||||
const tableQuery = getBillTableQuery(state, props);
|
||||
|
||||
const mapped = {
|
||||
billsCurrentPage: getBillsItems(state, props, query),
|
||||
billsCurrentPage: getBillsItems(state, props, tableQuery),
|
||||
billsViews: getResourceViews(state, props, 'bills'),
|
||||
billsItems: state.bills.items,
|
||||
billsTableQuery: query,
|
||||
billsPageination: getBillsPaginationMeta(state, props, query),
|
||||
billsTableQuery: tableQuery,
|
||||
|
||||
// @todo un-unncessery shit.
|
||||
billsPageination: getBillsPaginationMeta(state, props, tableQuery),
|
||||
billsLoading: state.bills.loading,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
import React, {
|
||||
useMemo,
|
||||
useState,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import { useFormik } from 'formik';
|
||||
import moment from 'moment';
|
||||
import { Intent, FormGroup, TextArea } from '@blueprintjs/core';
|
||||
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import { pick, omit } from 'lodash';
|
||||
|
||||
import BillFormHeader from './BillFormHeader';
|
||||
import EstimatesItemsTable from 'containers/Sales/Estimate/EntriesItemsTable';
|
||||
import BillFormFooter from './BillFormFooter';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withMediaActions from 'containers/Media/withMediaActions';
|
||||
import withBillActions from './withBillActions';
|
||||
|
||||
import { AppToaster } from 'components';
|
||||
import Dragzone from 'components/Dragzone';
|
||||
import useMedia from 'hooks/useMedia';
|
||||
|
||||
import { compose, repeatValue } from 'utils';
|
||||
|
||||
const MIN_LINES_NUMBER = 4;
|
||||
|
||||
function BillForm({
|
||||
//#WithMedia
|
||||
requestSubmitMedia,
|
||||
requestDeleteMedia,
|
||||
|
||||
//#withBillActions
|
||||
requestSubmitBill,
|
||||
|
||||
//#withDashboard
|
||||
changePageTitle,
|
||||
changePageSubtitle,
|
||||
|
||||
//#withBillDetail
|
||||
bill,
|
||||
|
||||
//#Own Props
|
||||
onFormSubmit,
|
||||
onCancelForm,
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
const [payload, setPayload] = useState({});
|
||||
|
||||
const {
|
||||
setFiles,
|
||||
saveMedia,
|
||||
deletedFiles,
|
||||
setDeletedFiles,
|
||||
deleteMedia,
|
||||
} = useMedia({
|
||||
saveCallback: requestSubmitMedia,
|
||||
deleteCallback: requestDeleteMedia,
|
||||
});
|
||||
|
||||
const handleDropFiles = useCallback((_files) => {
|
||||
setFiles(_files.filter((file) => file.uploaded === false));
|
||||
}, []);
|
||||
|
||||
const savedMediaIds = useRef([]);
|
||||
const clearSavedMediaIds = () => {
|
||||
savedMediaIds.current = [];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (bill && bill.id) {
|
||||
changePageTitle(formatMessage({ id: 'edit_bill' }));
|
||||
} else {
|
||||
changePageTitle(formatMessage({ id: 'new_bill' }));
|
||||
}
|
||||
});
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
vendor_id: Yup.number()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'vendor_name_' })),
|
||||
bill_date: Yup.date()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'bill_date_' })),
|
||||
due_date: Yup.date()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'due_date_' })),
|
||||
bill_number: Yup.number()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'bill_number_' })),
|
||||
reference_no: Yup.string().min(1).max(255),
|
||||
status: Yup.string().required(),
|
||||
note: Yup.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(1024)
|
||||
.label(formatMessage({ id: 'note' })),
|
||||
|
||||
entries: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
quantity: Yup.number().nullable(),
|
||||
rate: Yup.number().nullable(),
|
||||
item_id: Yup.number()
|
||||
.nullable()
|
||||
.when(['quantity', 'rate'], {
|
||||
is: (quantity, rate) => quantity || rate,
|
||||
then: Yup.number().required(),
|
||||
}),
|
||||
total: Yup.number().nullable(),
|
||||
discount: Yup.number().nullable(),
|
||||
description: Yup.string().nullable(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const saveBillSubmit = useCallback(
|
||||
(payload) => {
|
||||
onFormSubmit && onFormSubmit(payload);
|
||||
},
|
||||
[onFormSubmit],
|
||||
);
|
||||
|
||||
const defaultBill = useMemo(() => ({
|
||||
index: 0,
|
||||
item_id: null,
|
||||
rate: null,
|
||||
discount: null,
|
||||
quantity: null,
|
||||
description: '',
|
||||
status: '',
|
||||
}));
|
||||
|
||||
const defaultInitialValues = useMemo(
|
||||
() => ({
|
||||
accept: '',
|
||||
vendor_name: '',
|
||||
bill_number: '',
|
||||
bill_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
due_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
reference_no: '',
|
||||
note: '',
|
||||
entries: [...repeatValue(defaultBill, MIN_LINES_NUMBER)],
|
||||
}),
|
||||
[defaultBill],
|
||||
);
|
||||
|
||||
const orderingIndex = (_invoice) => {
|
||||
return _invoice.map((item, index) => ({
|
||||
...item,
|
||||
index: index + 1,
|
||||
}));
|
||||
};
|
||||
|
||||
const initialValues = useMemo(
|
||||
() => ({
|
||||
...defaultInitialValues,
|
||||
entries: orderingIndex(defaultInitialValues.entries),
|
||||
}),
|
||||
[defaultInitialValues],
|
||||
);
|
||||
|
||||
const initialAttachmentFiles = useMemo(() => {
|
||||
return bill && bill.media
|
||||
? bill.media.map((attach) => ({
|
||||
preview: attach.attachment_file,
|
||||
uploaded: true,
|
||||
metadata: { ...attach },
|
||||
}))
|
||||
: [];
|
||||
}, [bill]);
|
||||
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
validationSchema,
|
||||
initialValues: {
|
||||
...initialValues,
|
||||
},
|
||||
onSubmit: async (values, { setSubmitting, setErrors, resetForm }) => {
|
||||
setSubmitting(true);
|
||||
const entries = values.entries.map((item) => omit(item, ['total']));
|
||||
|
||||
const form = {
|
||||
...values,
|
||||
entries,
|
||||
};
|
||||
const saveBill = (mediaIds) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const requestForm = { ...form, media_ids: mediaIds };
|
||||
|
||||
requestSubmitBill(requestForm)
|
||||
.then((response) => {
|
||||
AppToaster.show({
|
||||
message: formatMessage(
|
||||
{ id: 'the_bill_has_been_successfully_created' },
|
||||
{ number: values.bill_number },
|
||||
),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
setSubmitting(false);
|
||||
resetForm();
|
||||
saveBillSubmit({ action: 'new', ...payload });
|
||||
clearSavedMediaIds();
|
||||
})
|
||||
.catch((errors) => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
});
|
||||
|
||||
Promise.all([saveMedia(), deleteMedia()])
|
||||
.then(([savedMediaResponses]) => {
|
||||
const mediaIds = savedMediaResponses.map((res) => res.data.media.id);
|
||||
savedMediaIds.current = mediaIds;
|
||||
return savedMediaResponses;
|
||||
})
|
||||
.then(() => {
|
||||
return saveBill(savedMediaIds.current);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmitClick = useCallback(
|
||||
(payload) => {
|
||||
setPayload(payload);
|
||||
formik.submitForm();
|
||||
},
|
||||
[setPayload, formik],
|
||||
);
|
||||
|
||||
const handleCancelClick = useCallback(
|
||||
(payload) => {
|
||||
onCancelForm && onCancelForm(payload);
|
||||
},
|
||||
[onCancelForm],
|
||||
);
|
||||
|
||||
console.log(formik.errors, 'Bill');
|
||||
const handleDeleteFile = useCallback(
|
||||
(_deletedFiles) => {
|
||||
_deletedFiles.forEach((deletedFile) => {
|
||||
if (deletedFile.uploaded && deletedFile.metadata.id) {
|
||||
setDeletedFiles([...deletedFiles, deletedFile.metadata.id]);
|
||||
}
|
||||
});
|
||||
},
|
||||
[setDeletedFiles, deletedFiles],
|
||||
);
|
||||
|
||||
const onClickCleanAllLines = () => {
|
||||
formik.setFieldValue(
|
||||
'entries',
|
||||
orderingIndex([...repeatValue(defaultBill, MIN_LINES_NUMBER)]),
|
||||
);
|
||||
};
|
||||
|
||||
const onClickAddNewRow = () => {
|
||||
formik.setFieldValue(
|
||||
'entries',
|
||||
orderingIndex([...formik.values.entries, defaultBill]),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'bill-form'}>
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<BillFormHeader formik={formik} />
|
||||
<EstimatesItemsTable
|
||||
formik={formik}
|
||||
entries={formik.values.entries}
|
||||
onClickAddNewRow={onClickAddNewRow}
|
||||
onClickClearAllLines={onClickCleanAllLines}
|
||||
/>
|
||||
<FormGroup label={<T id={'note'} />} className={'form-group--'}>
|
||||
<TextArea growVertically={true} {...formik.getFieldProps('note')} />
|
||||
</FormGroup>
|
||||
<Dragzone
|
||||
initialFiles={initialAttachmentFiles}
|
||||
onDrop={handleDropFiles}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
hint={'Attachments: Maxiumum size: 20MB'}
|
||||
/>
|
||||
<BillFormFooter
|
||||
formik={formik}
|
||||
onSubmit={handleSubmitClick}
|
||||
onCancelClick={handleCancelClick}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withBillActions,
|
||||
withDashboardActions,
|
||||
withMediaActions,
|
||||
)(BillForm);
|
||||
@@ -1,185 +0,0 @@
|
||||
import React, { useMemo, useCallback, useState } from 'react';
|
||||
import {
|
||||
FormGroup,
|
||||
InputGroup,
|
||||
Intent,
|
||||
Position,
|
||||
MenuItem,
|
||||
Classes,
|
||||
} from '@blueprintjs/core';
|
||||
import { DateInput } from '@blueprintjs/datetime';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import { Row, Col } from 'react-grid-system';
|
||||
import moment from 'moment';
|
||||
import { momentFormatter, compose, tansformDateValue } from 'utils';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
AccountsSelectList,
|
||||
ListSelect,
|
||||
ErrorMessage,
|
||||
FieldRequiredHint,
|
||||
Hint,
|
||||
} from 'components';
|
||||
|
||||
import withCustomers from 'containers/Customers/withCustomers';
|
||||
import withAccounts from 'containers/Accounts/withAccounts';
|
||||
|
||||
function BillFormHeader({
|
||||
formik: { errors, touched, setFieldValue, getFieldProps, values },
|
||||
|
||||
//#withCustomers
|
||||
customers,
|
||||
//#withAccouts
|
||||
accountsList,
|
||||
}) {
|
||||
const handleDateChange = useCallback(
|
||||
(date_filed) => (date) => {
|
||||
const formatted = moment(date).format('YYYY-MM-DD');
|
||||
setFieldValue(date_filed, formatted);
|
||||
},
|
||||
[setFieldValue],
|
||||
);
|
||||
|
||||
const onChangeSelected = useCallback(
|
||||
(filedName) => {
|
||||
return (item) => {
|
||||
setFieldValue(filedName, item.id);
|
||||
};
|
||||
},
|
||||
[setFieldValue],
|
||||
);
|
||||
|
||||
const vendorNameRenderer = useCallback(
|
||||
(accept, { handleClick }) => (
|
||||
<MenuItem
|
||||
key={accept.id}
|
||||
text={accept.display_name}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
// Filter vendor name
|
||||
const filterVendorAccount = (query, vendor, _index, exactMatch) => {
|
||||
const normalizedTitle = vendor.display_name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return (
|
||||
`${vendor.display_name} ${normalizedTitle}`.indexOf(normalizedQuery) >=
|
||||
0
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{/* vendor account name */}
|
||||
<FormGroup
|
||||
label={<T id={'vendor_name'} />}
|
||||
inline={true}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
intent={errors.vendor_id && touched.vendor_id && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage name={'vendor_id'} {...{ errors, touched }} />
|
||||
}
|
||||
>
|
||||
<ListSelect
|
||||
items={customers}
|
||||
noResults={<MenuItem disabled={true} text="No results." />}
|
||||
itemRenderer={vendorNameRenderer}
|
||||
itemPredicate={filterVendorAccount}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={onChangeSelected('vendor_id')}
|
||||
selectedItem={values.vendor_id}
|
||||
selectedItemProp={'id'}
|
||||
defaultText={<T id={'select_vendor_account'} />}
|
||||
labelProp={'display_name'}
|
||||
/>
|
||||
</FormGroup>
|
||||
<Row>
|
||||
<Col>
|
||||
<FormGroup
|
||||
label={<T id={'bill_date'} />}
|
||||
inline={true}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
intent={errors.bill_date && touched.bill_date && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage name="bill_date" {...{ errors, touched }} />
|
||||
}
|
||||
>
|
||||
<DateInput
|
||||
{...momentFormatter('YYYY/MM/DD')}
|
||||
value={tansformDateValue(values.bill_date)}
|
||||
onChange={handleDateChange('bill_date')}
|
||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col>
|
||||
<FormGroup
|
||||
label={<T id={'due_date'} />}
|
||||
inline={true}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
intent={errors.due_date && touched.due_date && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage name="due_date" {...{ errors, touched }} />
|
||||
}
|
||||
>
|
||||
<DateInput
|
||||
{...momentFormatter('YYYY/MM/DD')}
|
||||
value={tansformDateValue(values.due_date)}
|
||||
onChange={handleDateChange('due_date')}
|
||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
{/* bill number */}
|
||||
<FormGroup
|
||||
label={<T id={'bill_number'} />}
|
||||
inline={true}
|
||||
className={('form-group--estimate', Classes.FILL)}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
intent={errors.bill_number && touched.bill_number && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage name="bill_number" {...{ errors, touched }} />
|
||||
}
|
||||
>
|
||||
<InputGroup
|
||||
intent={errors.bill_number && touched.bill_number && Intent.DANGER}
|
||||
minimal={true}
|
||||
{...getFieldProps('bill_number')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
<FormGroup
|
||||
label={<T id={'reference'} />}
|
||||
inline={true}
|
||||
className={classNames('form-group--reference', Classes.FILL)}
|
||||
intent={errors.reference_no && touched.reference_no && Intent.DANGER}
|
||||
helperText={<ErrorMessage name="reference" {...{ errors, touched }} />}
|
||||
>
|
||||
<InputGroup
|
||||
intent={errors.reference_no && touched.reference_no && Intent.DANGER}
|
||||
minimal={true}
|
||||
{...getFieldProps('reference_no')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withCustomers(({ customers }) => ({
|
||||
customers,
|
||||
})),
|
||||
withAccounts(({ accountsList }) => ({
|
||||
accountsList,
|
||||
})),
|
||||
)(BillFormHeader);
|
||||
@@ -1,62 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import BillForm from './BillForm';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
|
||||
import withCustomersActions from 'containers/Customers/withCustomersActions';
|
||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||
import withItemsActions from 'containers/Items/withItemsActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
function Bills({
|
||||
//#withwithAccountsActions
|
||||
requestFetchAccounts,
|
||||
|
||||
//#withCustomersActions
|
||||
requestFetchCustomers,
|
||||
|
||||
//#withItemsActions
|
||||
requestFetchItems,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
// Handle fetch accounts
|
||||
const fetchAccounts = useQuery('accounts-list', (key) =>
|
||||
requestFetchAccounts(),
|
||||
);
|
||||
|
||||
// Handle fetch customers data table
|
||||
const fetchCustomers = useQuery('customers-table', () =>
|
||||
requestFetchCustomers({}),
|
||||
);
|
||||
|
||||
// Handle fetch Items data table or list
|
||||
const fetchItems = useQuery('items-table', () => requestFetchItems({}));
|
||||
|
||||
const handleFormSubmit = useCallback((payload) => {}, [history]);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
history.goBack();
|
||||
}, [history]);
|
||||
|
||||
return (
|
||||
<DashboardInsider
|
||||
loading={
|
||||
fetchCustomers.isFetching ||
|
||||
fetchItems.isFetching ||
|
||||
fetchAccounts.isFetching
|
||||
}
|
||||
>
|
||||
<BillForm onSubmit={handleFormSubmit} onCancel={handleCancel} />
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withCustomersActions,
|
||||
withItemsActions,
|
||||
withAccountsActions,
|
||||
)(Bills);
|
||||
93
client/src/containers/Purchases/PaymentMades/PaymentMade.js
Normal file
93
client/src/containers/Purchases/PaymentMades/PaymentMade.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import PaymentMadeForm from './PaymentMadeForm';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
|
||||
import withVenderActions from 'containers/Vendors/withVendorActions';
|
||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||
import withItemsActions from 'containers/Items/withItemsActions';
|
||||
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||
import withBillActions from '../Bill/withBillActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
function PaymentMade({
|
||||
//#withwithAccountsActions
|
||||
requestFetchAccounts,
|
||||
|
||||
//#withVenderActions
|
||||
requestFetchVendorsTable,
|
||||
|
||||
//#withItemsActions
|
||||
requestFetchItems,
|
||||
|
||||
//#withPaymentMadesActions
|
||||
requestFetchPaymentMade,
|
||||
|
||||
//#withBillActions
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { id } = useParams();
|
||||
const [venderId, setVenderId] = useState(null);
|
||||
|
||||
// Handle fetch accounts data
|
||||
const fetchAccounts = useQuery('accounts-list', (key) =>
|
||||
requestFetchAccounts(),
|
||||
);
|
||||
|
||||
// Handle fetch Items data table or list
|
||||
const fetchItems = useQuery('items-list', () => requestFetchItems({}));
|
||||
|
||||
// Handle fetch venders data table or list
|
||||
const fetchVenders = useQuery('venders-list', () =>
|
||||
requestFetchVendorsTable({}),
|
||||
);
|
||||
|
||||
const fetchPaymentMade = useQuery(
|
||||
['payment-made', id],
|
||||
(key, _id) => requestFetchPaymentMade(_id),
|
||||
{ enabled: !!id },
|
||||
);
|
||||
|
||||
const handleFormSubmit = useCallback(
|
||||
(payload) => {
|
||||
payload.redirect && history.push('/payment-mades');
|
||||
},
|
||||
[history],
|
||||
);
|
||||
const handleCancel = useCallback(() => {
|
||||
history.goBack();
|
||||
}, [history]);
|
||||
|
||||
const handleVenderChange = (venderId) => {
|
||||
setVenderId(venderId);
|
||||
};
|
||||
return (
|
||||
<DashboardInsider
|
||||
loading={
|
||||
fetchVenders.isFetching ||
|
||||
fetchItems.isFetching ||
|
||||
fetchAccounts.isFetching ||
|
||||
fetchPaymentMade.isFetching
|
||||
}
|
||||
name={'payment-made'}
|
||||
>
|
||||
<PaymentMadeForm
|
||||
onFormSubmit={handleFormSubmit}
|
||||
paymentMadeId={id}
|
||||
onCancelForm={handleCancel}
|
||||
onVenderChange={handleVenderChange}
|
||||
/>
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withVenderActions,
|
||||
withItemsActions,
|
||||
withAccountsActions,
|
||||
withBillActions,
|
||||
withPaymentMadeActions,
|
||||
)(PaymentMade);
|
||||
@@ -0,0 +1,150 @@
|
||||
import React, { useCallback, useState, useMemo } from 'react';
|
||||
import Icon from 'components/Icon';
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Popover,
|
||||
NavbarDivider,
|
||||
NavbarGroup,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useRouteMatch, useHistory } from 'react-router-dom';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import FilterDropdown from 'components/FilterDropdown';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
|
||||
import { If, DashboardActionViewsList } from 'components';
|
||||
|
||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
||||
import withPaymentMade from './withPaymentMade';
|
||||
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
function PaymentMadeActionsBar({
|
||||
//#withResourceDetail
|
||||
resourceFields,
|
||||
|
||||
//#withPaymentMades
|
||||
paymentMadeViews,
|
||||
|
||||
//#withPaymentMadesActions
|
||||
addPaymentMadesTableQueries,
|
||||
|
||||
// #own Porps
|
||||
onFilterChanged,
|
||||
selectedRows = [],
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { path } = useRouteMatch();
|
||||
const [filterCount, setFilterCount] = useState(0);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const handleClickNewPaymentMade = useCallback(() => {
|
||||
history.push('/payment-made/new');
|
||||
}, [history]);
|
||||
|
||||
// const filterDropdown = FilterDropdown({
|
||||
// initialCondition: {
|
||||
// fieldKey: '',
|
||||
// compatator: 'contains',
|
||||
// value: '',
|
||||
// },
|
||||
// fields: resourceFields,
|
||||
// onFilterChange: (filterConditions) => {
|
||||
// addPaymentMadesTableQueries({
|
||||
// filter_roles: filterConditions || '',
|
||||
// });
|
||||
// onFilterChanged && onFilterChanged(filterConditions);
|
||||
// },
|
||||
// });
|
||||
|
||||
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
|
||||
selectedRows,
|
||||
]);
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<DashboardActionViewsList
|
||||
resourceName={'bill_payments'}
|
||||
views={paymentMadeViews}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'plus'} />}
|
||||
text={<T id={'new_payment_made'} />}
|
||||
onClick={handleClickNewPaymentMade}
|
||||
/>
|
||||
<Popover
|
||||
minimal={true}
|
||||
// content={filterDropdown}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
>
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL)}
|
||||
text={
|
||||
filterCount <= 0 ? (
|
||||
<T id={'filter'} />
|
||||
) : (
|
||||
`${filterCount} ${formatMessage({ id: 'filters_applied' })}`
|
||||
)
|
||||
}
|
||||
icon={<Icon icon={'filter-16'} iconSize={16} />}
|
||||
/>
|
||||
</Popover>
|
||||
<If condition={hasSelectedRows}>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
// onClick={handleBulkDelete}
|
||||
/>
|
||||
</If>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'print-16'} iconSize={'16'} />}
|
||||
text={<T id={'print'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-import-16'} />}
|
||||
text={<T id={'import'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
resourceName: 'bill_payments',
|
||||
});
|
||||
|
||||
const withPaymentMadeActionsBar = connect(mapStateToProps);
|
||||
|
||||
export default compose(
|
||||
withPaymentMadeActionsBar,
|
||||
withResourceDetail(({ resourceFields }) => ({
|
||||
resourceFields,
|
||||
})),
|
||||
withPaymentMade(({ paymentMadeViews }) => ({
|
||||
paymentMadeViews,
|
||||
})),
|
||||
withPaymentMadeActions,
|
||||
)(PaymentMadeActionsBar);
|
||||
@@ -0,0 +1,242 @@
|
||||
import React, { useEffect, useCallback, useState, useMemo } from 'react';
|
||||
import {
|
||||
Intent,
|
||||
Button,
|
||||
Classes,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { withRouter } from 'react-router';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import moment from 'moment';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import { DataTable, Money, Icon } from 'components';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withViewDetails from 'containers/Views/withViewDetails';
|
||||
|
||||
import withPaymentMade from './withPaymentMade';
|
||||
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||
import withCurrentView from 'containers/Views/withCurrentView';
|
||||
|
||||
function PaymentMadeDataTable({
|
||||
//#withPaymentMades
|
||||
paymentMadeCurrentPage,
|
||||
paymentMadePageination,
|
||||
paymentMadeLoading,
|
||||
paymentMadeItems,
|
||||
|
||||
// #withDashboardActions
|
||||
changeCurrentView,
|
||||
changePageSubtitle,
|
||||
setTopbarEditView,
|
||||
|
||||
// #withView
|
||||
viewMeta,
|
||||
|
||||
//#OwnProps
|
||||
loading,
|
||||
onFetchData,
|
||||
onEditPaymentMade,
|
||||
onDeletePaymentMade,
|
||||
onSelectedRowsChange,
|
||||
}) {
|
||||
const [initialMount, setInitialMount] = useState(false);
|
||||
const { custom_view_id: customViewId } = useParams();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
useEffect(() => {
|
||||
setInitialMount(false);
|
||||
}, [customViewId]);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
if (!paymentMadeLoading) {
|
||||
setInitialMount(true);
|
||||
}
|
||||
}, [paymentMadeLoading, setInitialMount]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (customViewId) {
|
||||
// changeCurrentView(customViewId);
|
||||
// setTopbarEditView(customViewId);
|
||||
// }
|
||||
// changePageSubtitle(customViewId && viewMeta ? viewMeta.name : '');
|
||||
// }, [
|
||||
// customViewId,
|
||||
// changeCurrentView,
|
||||
// changePageSubtitle,
|
||||
// setTopbarEditView,
|
||||
// viewMeta,
|
||||
// ]);
|
||||
|
||||
const handleEditPaymentMade = useCallback(
|
||||
(paymentMade) => () => {
|
||||
onEditPaymentMade && onEditPaymentMade(paymentMade);
|
||||
},
|
||||
[onEditPaymentMade],
|
||||
);
|
||||
|
||||
const handleDeletePaymentMade = useCallback(
|
||||
(paymentMade) => () => {
|
||||
onDeletePaymentMade && onDeletePaymentMade(paymentMade);
|
||||
},
|
||||
[onDeletePaymentMade],
|
||||
);
|
||||
|
||||
const actionMenuList = useCallback(
|
||||
(paymentMade) => (
|
||||
<Menu>
|
||||
<MenuItem text={formatMessage({ id: 'view_details' })} />
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'edit_payment_made' })}
|
||||
onClick={handleEditPaymentMade(paymentMade)}
|
||||
/>
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'delete_payment_made' })}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeletePaymentMade(paymentMade)}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
/>
|
||||
</Menu>
|
||||
),
|
||||
[handleDeletePaymentMade, handleEditPaymentMade, formatMessage],
|
||||
);
|
||||
|
||||
const onRowContextMenu = useCallback(
|
||||
(cell) => {
|
||||
return actionMenuList(cell.row.original);
|
||||
},
|
||||
[actionMenuList],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'payment_date',
|
||||
Header: formatMessage({ id: 'payment_date' }),
|
||||
accessor: (r) => moment(r.payment_date).format('YYYY MMM DD'),
|
||||
width: 140,
|
||||
className: 'payment_date',
|
||||
},
|
||||
{
|
||||
id: 'vendor_id',
|
||||
Header: formatMessage({ id: 'vendor_name' }),
|
||||
accessor: 'vendor.display_name',
|
||||
width: 140,
|
||||
className: 'vendor_id',
|
||||
},
|
||||
{
|
||||
id: 'payment_number',
|
||||
Header: formatMessage({ id: 'payment_number' }),
|
||||
accessor: (row) => `#${row.payment_number}`,
|
||||
width: 140,
|
||||
className: 'payment_number',
|
||||
},
|
||||
{
|
||||
id: 'payment_account_id',
|
||||
Header: formatMessage({ id: 'payment_account' }),
|
||||
accessor: 'payment_account.name',
|
||||
width: 140,
|
||||
className: 'payment_account_id',
|
||||
},
|
||||
{
|
||||
id: 'amount',
|
||||
Header: formatMessage({ id: 'amount' }),
|
||||
accessor: (r) => <Money amount={r.amount} currency={'USD'} />,
|
||||
width: 140,
|
||||
className: 'amount',
|
||||
},
|
||||
{
|
||||
id: 'reference',
|
||||
Header: formatMessage({ id: 'reference' }),
|
||||
accessor: 'reference',
|
||||
width: 140,
|
||||
className: 'reference',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'actions',
|
||||
Header: '',
|
||||
Cell: ({ cell }) => (
|
||||
<Popover
|
||||
content={actionMenuList(cell.row.original)}
|
||||
position={Position.RIGHT_BOTTOM}
|
||||
>
|
||||
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
||||
</Popover>
|
||||
),
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
disableResizing: true,
|
||||
},
|
||||
],
|
||||
[actionMenuList, formatMessage],
|
||||
);
|
||||
|
||||
const handleDataTableFetchData = useCallback(
|
||||
(...args) => {
|
||||
onFetchData && onFetchData(...args);
|
||||
},
|
||||
[onFetchData],
|
||||
);
|
||||
const handleSelectedRowsChange = useCallback(
|
||||
(selectedRows) => {
|
||||
onSelectedRowsChange &&
|
||||
onSelectedRowsChange(selectedRows.map((s) => s.original));
|
||||
},
|
||||
[onSelectedRowsChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LoadingIndicator loading={loading} mount={false}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={paymentMadeCurrentPage}
|
||||
onFetchData={handleDataTableFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
noInitialFetch={true}
|
||||
sticky={true}
|
||||
loading={paymentMadeLoading && !initialMount}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
rowContextMenu={onRowContextMenu}
|
||||
pagination={true}
|
||||
pagesCount={paymentMadePageination.pagesCount}
|
||||
initialPageSize={paymentMadePageination.pageSize}
|
||||
initialPageIndex={paymentMadePageination.page - 1}
|
||||
/>
|
||||
</LoadingIndicator>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
withCurrentView,
|
||||
withDialogActions,
|
||||
withDashboardActions,
|
||||
withPaymentMadeActions,
|
||||
withPaymentMade(
|
||||
({
|
||||
paymentMadeCurrentPage,
|
||||
paymentMadeLoading,
|
||||
paymentMadePageination,
|
||||
}) => ({
|
||||
paymentMadeCurrentPage,
|
||||
paymentMadeLoading,
|
||||
paymentMadePageination,
|
||||
}),
|
||||
),
|
||||
withViewDetails(),
|
||||
)(PaymentMadeDataTable);
|
||||
320
client/src/containers/Purchases/PaymentMades/PaymentMadeForm.js
Normal file
320
client/src/containers/Purchases/PaymentMades/PaymentMadeForm.js
Normal file
@@ -0,0 +1,320 @@
|
||||
import React, {
|
||||
useMemo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
useRef,
|
||||
} from 'react';
|
||||
|
||||
import * as Yup from 'yup';
|
||||
import { useFormik } from 'formik';
|
||||
import moment from 'moment';
|
||||
import { Intent, FormGroup, TextArea } from '@blueprintjs/core';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import { pick, values } from 'lodash';
|
||||
|
||||
import PaymentMadeHeader from './PaymentMadeFormHeader';
|
||||
import PaymentMadeItemsTable from './PaymentMadeItemsTable';
|
||||
import PaymentMadeFooter from './PaymentMadeFormFooter';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withMediaActions from 'containers/Media/withMediaActions';
|
||||
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||
import withPaymentMadeDetail from './withPaymentMadeDetail';
|
||||
import withPaymentMade from './withPaymentMade';
|
||||
import { AppToaster } from 'components';
|
||||
import Dragzone from 'components/Dragzone';
|
||||
import useMedia from 'hooks/useMedia';
|
||||
|
||||
import { compose, repeatValue } from 'utils';
|
||||
|
||||
const MIN_LINES_NUMBER = 5;
|
||||
|
||||
function PaymentMadeForm({
|
||||
//#withMedia
|
||||
requestSubmitMedia,
|
||||
requestDeleteMedia,
|
||||
//#withPaymentMadesActions
|
||||
|
||||
requestSubmitPaymentMade,
|
||||
requestEditPaymentMade,
|
||||
|
||||
//#withDashboard
|
||||
changePageTitle,
|
||||
changePageSubtitle,
|
||||
|
||||
//#withPaymentMadeDetail
|
||||
paymentMade,
|
||||
|
||||
onFormSubmit,
|
||||
onCancelForm,
|
||||
onVenderChange,
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
const [payload, setPayload] = useState({});
|
||||
const { id } = useParams();
|
||||
const {
|
||||
setFiles,
|
||||
saveMedia,
|
||||
deletedFiles,
|
||||
setDeletedFiles,
|
||||
deleteMedia,
|
||||
} = useMedia({
|
||||
saveCallback: requestSubmitMedia,
|
||||
deleteCallback: requestDeleteMedia,
|
||||
});
|
||||
|
||||
const savedMediaIds = useRef([]);
|
||||
const clearSavedMediaIds = () => {
|
||||
savedMediaIds.current = [];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onVenderChange && onVenderChange(formik.values.vendor_id);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (paymentMade && paymentMade.id) {
|
||||
changePageTitle(formatMessage({ id: 'edit_payment_made' }));
|
||||
} else {
|
||||
changePageTitle(formatMessage({ id: 'payment_made' }));
|
||||
}
|
||||
}, [changePageTitle, paymentMade, formatMessage]);
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
vendor_id: Yup.string()
|
||||
.label(formatMessage({ id: 'vendor_name_' }))
|
||||
.required(),
|
||||
payment_date: Yup.date()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'payment_date_' })),
|
||||
payment_account_id: Yup.number()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'payment_account_' })),
|
||||
payment_number: Yup.number()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'payment_no_' })),
|
||||
reference: Yup.string().min(1).max(255).nullable(),
|
||||
description: Yup.string(),
|
||||
entries: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
payment_amount: Yup.number().nullable(),
|
||||
bill_number: Yup.number().nullable(),
|
||||
amount: Yup.number().nullable(),
|
||||
due_amount: Yup.number().nullable(),
|
||||
bill_date: Yup.date(),
|
||||
bill_id: Yup.number()
|
||||
.nullable()
|
||||
.when(['payment_amount'], {
|
||||
is: (payment_amount) => payment_amount,
|
||||
then: Yup.number().required(),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
});
|
||||
const handleDropFiles = useCallback((_files) => {
|
||||
setFiles(_files.filter((file) => file.uploaded === false));
|
||||
}, []);
|
||||
|
||||
const savePaymentMadeSubmit = useCallback((payload) => {
|
||||
onFormSubmit && onFormSubmit(payload);
|
||||
});
|
||||
|
||||
const defaultPaymentMade = useMemo(
|
||||
() => ({
|
||||
bill_id: '',
|
||||
bill_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
bill_number: '',
|
||||
amount: '',
|
||||
due_amount: '',
|
||||
payment_amount: '',
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const defaultInitialValues = useMemo(
|
||||
() => ({
|
||||
vendor_id: '',
|
||||
payment_account_id: '',
|
||||
payment_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
reference: '',
|
||||
payment_number: '',
|
||||
// receive_amount: '',
|
||||
description: '',
|
||||
entries: [...repeatValue(defaultPaymentMade, MIN_LINES_NUMBER)],
|
||||
}),
|
||||
[defaultPaymentMade],
|
||||
);
|
||||
|
||||
const orderingIndex = (_entries) => {
|
||||
return _entries.map((item, index) => ({
|
||||
...item,
|
||||
index: index + 1,
|
||||
}));
|
||||
};
|
||||
|
||||
const initialValues = useMemo(
|
||||
() => ({
|
||||
...(paymentMade
|
||||
? {
|
||||
...pick(paymentMade, Object.keys(defaultInitialValues)),
|
||||
entries: [
|
||||
...paymentMade.entries.map((paymentMade) => ({
|
||||
...pick(paymentMade, Object.keys(defaultPaymentMade)),
|
||||
})),
|
||||
...repeatValue(
|
||||
defaultPaymentMade,
|
||||
Math.max(MIN_LINES_NUMBER - paymentMade.entries.length, 0),
|
||||
),
|
||||
],
|
||||
}
|
||||
: {
|
||||
...defaultInitialValues,
|
||||
entries: orderingIndex(defaultInitialValues.entries),
|
||||
}),
|
||||
}),
|
||||
[paymentMade, defaultInitialValues, defaultPaymentMade],
|
||||
);
|
||||
|
||||
const initialAttachmentFiles = useMemo(() => {
|
||||
return paymentMade && paymentMade.media
|
||||
? paymentMade.media.map((attach) => ({
|
||||
preview: attach.attachment_file,
|
||||
uploaded: true,
|
||||
metadata: { ...attach },
|
||||
}))
|
||||
: [];
|
||||
}, [paymentMade]);
|
||||
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
validationSchema,
|
||||
initialValues: {
|
||||
...initialValues,
|
||||
},
|
||||
onSubmit: async (values, { setSubmitting, setErrors, resetForm }) => {
|
||||
setSubmitting(true);
|
||||
const entries = formik.values.entries.filter((item) => {
|
||||
if (item.bill_id !== undefined) {
|
||||
return { ...item };
|
||||
}
|
||||
});
|
||||
const form = {
|
||||
...values,
|
||||
entries,
|
||||
};
|
||||
|
||||
const requestForm = { ...form };
|
||||
|
||||
if (paymentMade && paymentMade.id) {
|
||||
requestEditPaymentMade(paymentMade.id, requestForm)
|
||||
.then((response) => {
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'the_payment_made_has_been_successfully_edited',
|
||||
}),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
setSubmitting(false);
|
||||
savePaymentMadeSubmit({ action: 'update', ...payload });
|
||||
resetForm();
|
||||
})
|
||||
.catch((error) => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
} else {
|
||||
requestSubmitPaymentMade(requestForm)
|
||||
.then((response) => {
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'the_payment_made_has_been_successfully_created',
|
||||
}),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
setSubmitting(false);
|
||||
resetForm();
|
||||
savePaymentMadeSubmit({ action: 'new', ...payload });
|
||||
})
|
||||
.catch((errors) => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const handleDeleteFile = useCallback(
|
||||
(_deletedFiles) => {
|
||||
_deletedFiles.forEach((deletedFile) => {
|
||||
if (deletedFile.upload && deletedFile.metadata.id) {
|
||||
setDeletedFiles([...deletedFiles, deletedFile.metadata.id]);
|
||||
}
|
||||
});
|
||||
},
|
||||
[setDeletedFiles, deletedFiles],
|
||||
);
|
||||
|
||||
const handleSubmitClick = useCallback(
|
||||
(payload) => {
|
||||
setPayload(payload);
|
||||
formik.submitForm();
|
||||
},
|
||||
[setPayload, formik],
|
||||
);
|
||||
|
||||
const handleCancelClick = useCallback(
|
||||
(payload) => {
|
||||
onCancelForm && onCancelForm(payload);
|
||||
},
|
||||
[onCancelForm],
|
||||
);
|
||||
|
||||
const handleClickAddNewRow = () => {
|
||||
formik.setFieldValue(
|
||||
'entries',
|
||||
orderingIndex([...formik.values.entries, defaultPaymentMade]),
|
||||
);
|
||||
};
|
||||
|
||||
const handleClearAllLines = () => {
|
||||
formik.setFieldValue(
|
||||
'entries',
|
||||
orderingIndex([...repeatValue(defaultPaymentMade, MIN_LINES_NUMBER)]),
|
||||
);
|
||||
};
|
||||
console.log(formik.errors, 'ERROR');
|
||||
|
||||
return (
|
||||
<div className={'payment_made_form'}>
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<PaymentMadeHeader formik={formik} />
|
||||
<PaymentMadeItemsTable
|
||||
formik={formik}
|
||||
entries={formik.values.entries}
|
||||
// vendor_id={formik.values.vendor_id}
|
||||
onClickAddNewRow={handleClickAddNewRow}
|
||||
onClickClearAllLines={handleClearAllLines}
|
||||
/>
|
||||
|
||||
{/* <Dragzone
|
||||
initialFiles={initialAttachmentFiles}
|
||||
onDrop={handleDropFiles}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
hint={'Attachments: Maxiumum size: 20MB'}
|
||||
/> */}
|
||||
</form>
|
||||
<PaymentMadeFooter
|
||||
formik={formik}
|
||||
onSubmitClick={handleSubmitClick}
|
||||
onCancel={handleCancelClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withPaymentMadeActions,
|
||||
withDashboardActions,
|
||||
withMediaActions,
|
||||
withPaymentMadeDetail(),
|
||||
)(PaymentMadeForm);
|
||||
@@ -2,14 +2,22 @@ import React from 'react';
|
||||
import { Intent, Button } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
|
||||
export default function BillFormFooter({
|
||||
formik: { isSubmitting },
|
||||
export default function PaymentMadeFormFooter({
|
||||
formik: { isSubmitting, resetForm },
|
||||
onSubmitClick,
|
||||
onCancelClick,
|
||||
paymentMade,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<Button disabled={isSubmitting} intent={Intent.PRIMARY} type="submit">
|
||||
<div className={'estimate-form__floating-footer'}>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
onSubmitClick({ redirect: true });
|
||||
}}
|
||||
>
|
||||
<T id={'save_send'} />
|
||||
</Button>
|
||||
|
||||
@@ -19,11 +27,14 @@ export default function BillFormFooter({
|
||||
className={'ml1'}
|
||||
name={'save'}
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
onSubmitClick({ redirect: false });
|
||||
}}
|
||||
>
|
||||
<T id={'save'} />
|
||||
</Button>
|
||||
|
||||
<Button className={'ml1'} disabled={isSubmitting}>
|
||||
<Button className={'ml1'} disabled={isSubmitting} s>
|
||||
<T id={'clear'} />
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import React, { useMemo, useCallback, useState } from 'react';
|
||||
import {
|
||||
FormGroup,
|
||||
InputGroup,
|
||||
Intent,
|
||||
Position,
|
||||
MenuItem,
|
||||
Classes,
|
||||
} from '@blueprintjs/core';
|
||||
|
||||
import { DateInput } from '@blueprintjs/datetime';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
|
||||
import moment from 'moment';
|
||||
import { momentFormatter, compose, tansformDateValue } from 'utils';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
AccountsSelectList,
|
||||
ListSelect,
|
||||
ErrorMessage,
|
||||
FieldRequiredHint,
|
||||
} from 'components';
|
||||
|
||||
import withVender from 'containers/Vendors/withVendors';
|
||||
import withAccounts from 'containers/Accounts/withAccounts';
|
||||
|
||||
function PaymentMadeFormHeader({
|
||||
formik: { errors, touched, setFieldValue, getFieldProps, values },
|
||||
|
||||
//#withVender
|
||||
vendorsCurrentPage,
|
||||
//#withAccouts
|
||||
accountsList,
|
||||
}) {
|
||||
const handleDateChange = useCallback(
|
||||
(date_filed) => (date) => {
|
||||
const formatted = moment(date).format('YYYY-MM-DD');
|
||||
setFieldValue(date_filed, formatted);
|
||||
},
|
||||
[setFieldValue],
|
||||
);
|
||||
|
||||
const handleVenderRenderer = useCallback(
|
||||
(vender, { handleClick }) => (
|
||||
<MenuItem
|
||||
key={vender.id}
|
||||
text={vender.display_name}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
const handleFilterVender = (query, vender, index, exactMatch) => {
|
||||
const normalizedTitle = vender.display_name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return (
|
||||
`${vender.display_name} ${normalizedTitle}`.indexOf(normalizedQuery) >=
|
||||
0
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeSelect = useCallback(
|
||||
(filedName) => {
|
||||
return (item) => {
|
||||
setFieldValue(filedName, item.id);
|
||||
};
|
||||
},
|
||||
[setFieldValue],
|
||||
);
|
||||
// Filter Payment accounts.
|
||||
const paymentAccounts = useMemo(
|
||||
() => accountsList.filter((a) => a?.type?.key === 'current_asset'),
|
||||
[accountsList],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{/* Vend name */}
|
||||
<FormGroup
|
||||
label={<T id={'vendor_name'} />}
|
||||
inline={true}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
intent={errors.vendor_id && touched.vendor_id && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage name={'vendor_id'} {...{ errors, touched }} />
|
||||
}
|
||||
>
|
||||
<ListSelect
|
||||
items={vendorsCurrentPage}
|
||||
noResults={<MenuItem disabled={true} text="No results." />}
|
||||
itemRenderer={handleVenderRenderer}
|
||||
itemPredicate={handleFilterVender}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={onChangeSelect('vendor_id')}
|
||||
selectedItem={values.vendor_id}
|
||||
selectedItemProp={'id'}
|
||||
defaultText={<T id={'select_vender_account'} />}
|
||||
labelProp={'display_name'}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{/* Payment date */}
|
||||
<FormGroup
|
||||
label={<T id={'payment_date'} />}
|
||||
inline={true}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
intent={errors.payment_date && touched.payment_date && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage name="payment_date" {...{ errors, touched }} />
|
||||
}
|
||||
>
|
||||
<DateInput
|
||||
{...momentFormatter('YYYY/MM/DD')}
|
||||
value={tansformDateValue(values.payment_date)}
|
||||
onChange={handleDateChange('payment_date')}
|
||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{/* payment number */}
|
||||
<FormGroup
|
||||
label={<T id={'payment_no'} />}
|
||||
inline={true}
|
||||
className={('form-group--payment_number', Classes.FILL)}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
intent={
|
||||
errors.payment_number && touched.payment_number && Intent.DANGER
|
||||
}
|
||||
helperText={
|
||||
<ErrorMessage name="payment_number" {...{ errors, touched }} />
|
||||
}
|
||||
>
|
||||
<InputGroup
|
||||
intent={
|
||||
errors.payment_number && touched.payment_number && Intent.DANGER
|
||||
}
|
||||
minimal={true}
|
||||
{...getFieldProps('payment_number')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{/* payment account */}
|
||||
<FormGroup
|
||||
label={<T id={'payment_account'} />}
|
||||
className={classNames(
|
||||
'form-group--payment_account_id',
|
||||
'form-group--select-list',
|
||||
Classes.FILL,
|
||||
)}
|
||||
inline={true}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
intent={
|
||||
errors.payment_account_id &&
|
||||
touched.payment_account_id &&
|
||||
Intent.DANGER
|
||||
}
|
||||
helperText={
|
||||
<ErrorMessage
|
||||
name={'payment_account_id'}
|
||||
{...{ errors, touched }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<AccountsSelectList
|
||||
accounts={paymentAccounts}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
onAccountSelected={onChangeSelect('payment_account_id')}
|
||||
defaultSelectText={<T id={'select_payment_account'} />}
|
||||
selectedAccountId={values.payment_account_id}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
|
||||
{/* reference */}
|
||||
<FormGroup
|
||||
label={<T id={'reference'} />}
|
||||
inline={true}
|
||||
className={classNames('form-group--reference', Classes.FILL)}
|
||||
intent={errors.reference && touched.reference && Intent.DANGER}
|
||||
helperText={<ErrorMessage name="reference" {...{ errors, touched }} />}
|
||||
>
|
||||
<InputGroup
|
||||
intent={errors.reference && touched.reference && Intent.DANGER}
|
||||
minimal={true}
|
||||
{...getFieldProps('reference')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withVender(({ vendorsCurrentPage }) => ({
|
||||
vendorsCurrentPage,
|
||||
})),
|
||||
withAccounts(({ accountsList }) => ({
|
||||
accountsList,
|
||||
})),
|
||||
)(PaymentMadeFormHeader);
|
||||
@@ -0,0 +1,234 @@
|
||||
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import { Icon, DataTable } from 'components';
|
||||
import moment from 'moment';
|
||||
|
||||
import { compose, formattedAmount, transformUpdatedRows } from 'utils';
|
||||
import {
|
||||
MoneyFieldCell,
|
||||
DivFieldCell,
|
||||
EmptyDiv,
|
||||
} from 'components/DataTableCells';
|
||||
|
||||
import withBills from '../Bill/withBills';
|
||||
import { omit, pick } from 'lodash';
|
||||
|
||||
const ActionsCellRenderer = ({
|
||||
row: { index },
|
||||
column: { id },
|
||||
cell: { value },
|
||||
data,
|
||||
payload,
|
||||
}) => {
|
||||
if (data.length <= index + 1) {
|
||||
return '';
|
||||
}
|
||||
const onRemoveRole = () => {
|
||||
payload.removeRow(index);
|
||||
};
|
||||
return (
|
||||
<Tooltip content={<T id={'remove_the_line'} />} position={Position.LEFT}>
|
||||
<Button
|
||||
icon={<Icon icon={'times-circle'} iconSize={14} />}
|
||||
iconSize={14}
|
||||
className="m12"
|
||||
intent={Intent.DANGER}
|
||||
onClick={onRemoveRole}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const CellRenderer = (content, type) => (props) => {
|
||||
if (props.data.length === props.row.index + 1) {
|
||||
return '';
|
||||
}
|
||||
return content(props);
|
||||
};
|
||||
|
||||
const TotalCellRederer = (content, type) => (props) => {
|
||||
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;
|
||||
|
||||
return computed;
|
||||
}, 0);
|
||||
return <span>{formattedAmount(total, 'USD')}</span>;
|
||||
}
|
||||
return content(props);
|
||||
};
|
||||
|
||||
function PaymentMadeItemsTable({
|
||||
//#ownProps
|
||||
onClickRemoveRow,
|
||||
onClickAddNewRow,
|
||||
onClickClearAllLines,
|
||||
entries,
|
||||
formik: { errors, setFieldValue, values },
|
||||
}) {
|
||||
const [rows, setRows] = useState([]);
|
||||
const [entrie, setEntrie] = useState([]);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
useEffect(() => {
|
||||
setRows([...entries.map((e) => ({ ...e }))]);
|
||||
}, [entries]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: '#',
|
||||
accessor: 'index',
|
||||
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
|
||||
width: 40,
|
||||
disableResizing: true,
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'Date' }),
|
||||
id: 'bill_date',
|
||||
accessor: (r) => moment(r.bill_date).format('YYYY MMM DD'),
|
||||
Cell: CellRenderer(EmptyDiv, 'bill_date'),
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 250,
|
||||
},
|
||||
|
||||
{
|
||||
Header: formatMessage({ id: 'bill_number' }),
|
||||
accessor: (row) => `#${row.bill_number}`,
|
||||
Cell: CellRenderer(EmptyDiv, 'bill_number'),
|
||||
disableSortBy: true,
|
||||
className: 'bill_number',
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'bill_amount' }),
|
||||
accessor: 'amount',
|
||||
Cell: CellRenderer(DivFieldCell, 'amount'),
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
className: '',
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'amount_due' }),
|
||||
accessor: 'due_amount',
|
||||
Cell: TotalCellRederer(DivFieldCell, 'due_amount'),
|
||||
disableSortBy: true,
|
||||
width: 150,
|
||||
className: '',
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'payment_amount' }),
|
||||
accessor: 'payment_amount',
|
||||
Cell: TotalCellRederer(MoneyFieldCell, 'payment_amount'),
|
||||
|
||||
disableSortBy: true,
|
||||
width: 150,
|
||||
className: '',
|
||||
},
|
||||
{
|
||||
Header: '',
|
||||
accessor: 'action',
|
||||
Cell: ActionsCellRenderer,
|
||||
className: 'actions',
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 45,
|
||||
},
|
||||
],
|
||||
[formatMessage],
|
||||
);
|
||||
|
||||
const handleRemoveRow = useCallback(
|
||||
(rowIndex) => {
|
||||
if (rows.length <= 1) {
|
||||
return;
|
||||
}
|
||||
const removeIndex = parseInt(rowIndex, 10);
|
||||
const newRows = rows.filter((row, index) => index !== removeIndex);
|
||||
|
||||
setFieldValue(
|
||||
'entries',
|
||||
newRows.map((row, index) => ({
|
||||
...omit(row),
|
||||
index: index - 1,
|
||||
})),
|
||||
);
|
||||
onClickRemoveRow && onClickRemoveRow(removeIndex);
|
||||
},
|
||||
[entrie, setFieldValue, onClickRemoveRow],
|
||||
);
|
||||
|
||||
const onClickNewRow = () => {
|
||||
onClickAddNewRow && onClickAddNewRow();
|
||||
};
|
||||
|
||||
const handleClickClearAllLines = () => {
|
||||
onClickClearAllLines && onClickClearAllLines();
|
||||
};
|
||||
|
||||
const rowClassNames = useCallback((row) => {
|
||||
return { 'row--total': rows.length === row.index + 1 };
|
||||
});
|
||||
|
||||
const handleUpdateData = useCallback(
|
||||
(rowIndex, columnId, value) => {
|
||||
const newRows = rows.map((row, index) => {
|
||||
if (index === rowIndex) {
|
||||
return {
|
||||
...rows[rowIndex],
|
||||
[columnId]: value,
|
||||
};
|
||||
}
|
||||
return row;
|
||||
});
|
||||
// setRows(newRows);
|
||||
setFieldValue(
|
||||
'entries',
|
||||
newRows,
|
||||
// .map((row) => ({
|
||||
// ...pick(row, ['payment_amount']),
|
||||
// invoice_id: row.id,
|
||||
// })),
|
||||
);
|
||||
},
|
||||
[rows, setFieldValue, setRows],
|
||||
);
|
||||
return (
|
||||
<div className={'estimate-form__table'}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={rows}
|
||||
rowClassNames={rowClassNames}
|
||||
spinnerProps={false}
|
||||
payload={{
|
||||
errors: errors.entries || [],
|
||||
updateData: handleUpdateData,
|
||||
removeRow: handleRemoveRow,
|
||||
}}
|
||||
/>
|
||||
<div className={'mt1'}>
|
||||
<Button
|
||||
small={true}
|
||||
className={'button--secondary button--new-line'}
|
||||
onClick={onClickNewRow}
|
||||
>
|
||||
<T id={'new_lines'} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
small={true}
|
||||
className={'button--secondary button--clear-lines ml1'}
|
||||
onClick={handleClickClearAllLines}
|
||||
>
|
||||
<T id={'clear_all_lines'} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose()(PaymentMadeItemsTable);
|
||||
// withBills(({}) => ({}))
|
||||
184
client/src/containers/Purchases/PaymentMades/PaymentMadeList.js
Normal file
184
client/src/containers/Purchases/PaymentMades/PaymentMadeList.js
Normal file
@@ -0,0 +1,184 @@
|
||||
import React, { useEffect, useCallback, useMemo, useState } from 'react';
|
||||
import { Route, Switch, useHistory } from 'react-router-dom';
|
||||
import { useQuery, queryCache } from 'react-query';
|
||||
import { Alert, Intent } from '@blueprintjs/core';
|
||||
|
||||
import AppToaster from 'components/AppToaster';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
|
||||
import PaymentMadeDataTable from './PaymentMadeDataTable';
|
||||
import PaymentMadeActionsBar from './PaymentMadeActionsBar';
|
||||
import PaymentMadeViewTabs from './PaymentMadeViewTabs';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withResourceActions from 'containers/Resources/withResourcesActions';
|
||||
import withPaymentMades from './withPaymentMade';
|
||||
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||
import withViewsActions from 'containers/Views/withViewsActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
function PaymentMadeList({
|
||||
//#withDashboardActions
|
||||
changePageTitle,
|
||||
|
||||
//#withViewsActions
|
||||
requestFetchResourceViews,
|
||||
requestFetchResourceFields,
|
||||
|
||||
//#withPaymentMades
|
||||
paymentMadeTableQuery,
|
||||
|
||||
//#withPaymentMadesActions
|
||||
requestFetchPaymentMadesTable,
|
||||
requestDeletePaymentMade,
|
||||
addPaymentMadesTableQueries,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { formatMessage } = useIntl();
|
||||
const [deletePaymentMade, setDeletePaymentMade] = useState(false);
|
||||
const [selectedRows, setSelectedRows] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
changePageTitle(formatMessage({ id: 'payment_made_list' }));
|
||||
}, [changePageTitle, formatMessage]);
|
||||
|
||||
const fetchResourceViews = useQuery(
|
||||
['resource-views', 'bill_payments'],
|
||||
(key, resourceName) => requestFetchResourceViews(resourceName),
|
||||
);
|
||||
|
||||
const fetchResourceFields = useQuery(
|
||||
['resource-fields', 'bill_payments'],
|
||||
(key, resourceName) => requestFetchResourceFields(resourceName),
|
||||
);
|
||||
|
||||
const fetchPaymentMades = useQuery(
|
||||
['paymantMades-table', paymentMadeTableQuery],
|
||||
() => requestFetchPaymentMadesTable(),
|
||||
);
|
||||
|
||||
//handle dalete Payment Made
|
||||
const handleDeletePaymentMade = useCallback(
|
||||
(paymentMade) => {
|
||||
setDeletePaymentMade(paymentMade);
|
||||
},
|
||||
[setDeletePaymentMade],
|
||||
);
|
||||
|
||||
// handle cancel Payment Made
|
||||
const handleCancelPaymentMadeDelete = useCallback(() => {
|
||||
setDeletePaymentMade(false);
|
||||
}, [setDeletePaymentMade]);
|
||||
|
||||
// handleConfirm delete payment made
|
||||
const handleConfirmPaymentMadeDelete = useCallback(() => {
|
||||
requestDeletePaymentMade(deletePaymentMade.id).then(() => {
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'the_payment_made_has_been_successfully_deleted',
|
||||
}),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
setDeletePaymentMade(false);
|
||||
});
|
||||
}, [deletePaymentMade, requestDeletePaymentMade, formatMessage]);
|
||||
|
||||
const handleEditPaymentMade = useCallback((payment) => {
|
||||
history.push(`/payment-made/${payment.id}/edit`);
|
||||
});
|
||||
|
||||
// Calculates the selected rows count.
|
||||
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
|
||||
selectedRows,
|
||||
]);
|
||||
|
||||
const handleFetchData = useCallback(
|
||||
({ pageIndex, pageSize, sortBy }) => {
|
||||
const page = pageIndex + 1;
|
||||
|
||||
addPaymentMadesTableQueries({
|
||||
...(sortBy.length > 0
|
||||
? {
|
||||
column_sort_by: sortBy[0].id,
|
||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
||||
}
|
||||
: {}),
|
||||
page_size: pageSize,
|
||||
page,
|
||||
});
|
||||
},
|
||||
[addPaymentMadesTableQueries],
|
||||
);
|
||||
|
||||
// Handle filter change to re-fetch data-table.
|
||||
const handleFilterChanged = useCallback(() => {}, [fetchPaymentMades]);
|
||||
|
||||
// Handle selected rows change.
|
||||
const handleSelectedRowsChange = useCallback(
|
||||
(payment) => {
|
||||
setSelectedRows(payment);
|
||||
},
|
||||
[setSelectedRows],
|
||||
);
|
||||
|
||||
return (
|
||||
<DashboardInsider
|
||||
// loading={fetchResourceViews.isFetching || fetchResourceFields.isFetching}
|
||||
name={'payment_made'}
|
||||
>
|
||||
<PaymentMadeActionsBar
|
||||
// onBulkDelete={}
|
||||
selectedRows={selectedRows}
|
||||
onFilterChanged={handleFilterChanged}
|
||||
/>
|
||||
<DashboardPageContent>
|
||||
<Switch>
|
||||
<Route
|
||||
exact={true}
|
||||
path={[
|
||||
'/payment-mades/:custom_view_id/custom_view',
|
||||
'/payment-mades',
|
||||
]}
|
||||
>
|
||||
<PaymentMadeViewTabs />
|
||||
<PaymentMadeDataTable
|
||||
loading={fetchPaymentMades.isFetching}
|
||||
onDeletePaymentMade={handleDeletePaymentMade}
|
||||
onFetchData={handleFetchData}
|
||||
onEditPaymentMade={handleEditPaymentMade}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
/>
|
||||
</Route>
|
||||
</Switch>
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={<T id={'delete'} />}
|
||||
icon={'trash'}
|
||||
intent={Intent.DANGER}
|
||||
isOpen={deletePaymentMade}
|
||||
onCancel={handleCancelPaymentMadeDelete}
|
||||
onConfirm={handleConfirmPaymentMadeDelete}
|
||||
>
|
||||
<p>
|
||||
<T
|
||||
id={'once_delete_this_payment_made_you_will_able_to_restore_it'}
|
||||
/>
|
||||
</p>
|
||||
</Alert>
|
||||
</DashboardPageContent>
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withResourceActions,
|
||||
withPaymentMadeActions,
|
||||
withDashboardActions,
|
||||
withViewsActions,
|
||||
withPaymentMades(({ paymentMadeTableQuery }) => ({
|
||||
paymentMadeTableQuery,
|
||||
})),
|
||||
)(PaymentMadeList);
|
||||
@@ -0,0 +1,108 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
|
||||
import { useParams, withRouter } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { pick, debounce } from 'lodash';
|
||||
|
||||
import { DashboardViewsTabs } from 'components';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
|
||||
import withPaymentMade from './withPaymentMade';
|
||||
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withViewDetails from 'containers/Views/withViewDetails';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
function PaymentMadeViewTabs({
|
||||
//#withPaymentMades
|
||||
paymentMadeViews,
|
||||
|
||||
//#withPaymentMadesActions
|
||||
changePaymentMadeView,
|
||||
addPaymentMadesTableQueries,
|
||||
|
||||
// #withViewDetails
|
||||
viewItem,
|
||||
|
||||
// #withDashboardActions
|
||||
setTopbarEditView,
|
||||
changePageSubtitle,
|
||||
|
||||
//#Own Props
|
||||
onViewChanged,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { custom_view_id: customViewId = null } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
changePaymentMadeView(customViewId || -1);
|
||||
setTopbarEditView(customViewId);
|
||||
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
|
||||
|
||||
addPaymentMadesTableQueries({
|
||||
custom_view_id: customViewId,
|
||||
});
|
||||
return () => {
|
||||
setTopbarEditView(null);
|
||||
changePageSubtitle('');
|
||||
changePaymentMadeView(null);
|
||||
};
|
||||
}, [customViewId, addPaymentMadesTableQueries, changePaymentMadeView]);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
onViewChanged && onViewChanged(customViewId);
|
||||
}, [customViewId]);
|
||||
|
||||
const debounceChangeHistory = useRef(
|
||||
debounce((toUrl) => {
|
||||
history.push(toUrl);
|
||||
}, 250),
|
||||
);
|
||||
|
||||
const handleTabsChange = (viewId) => {
|
||||
const toPath = viewId ? `${viewId}/custom_view` : '';
|
||||
debounceChangeHistory.current(`/payment-mades/${toPath}`);
|
||||
setTopbarEditView(viewId);
|
||||
};
|
||||
const tabs = paymentMadeViews.map((view) => ({
|
||||
...pick(view, ['name', 'id']),
|
||||
}));
|
||||
|
||||
const handleClickNewView = () => {
|
||||
setTopbarEditView(null);
|
||||
history.push('/custom_views/payment-mades/new');
|
||||
};
|
||||
|
||||
return (
|
||||
<Navbar className={'navbar--dashboard-views'}>
|
||||
<NavbarGroup align={Alignment.LEFT}>
|
||||
<DashboardViewsTabs
|
||||
initialViewId={customViewId}
|
||||
baseUrl={'/payment-mades'}
|
||||
tabs={tabs}
|
||||
onNewViewTabClick={handleClickNewView}
|
||||
onChange={handleTabsChange}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
viewId: ownProps.match.params.custom_view_id,
|
||||
});
|
||||
|
||||
const withPaymentMadesViewTabs = connect(mapStateToProps);
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
withPaymentMadesViewTabs,
|
||||
withPaymentMadeActions,
|
||||
withDashboardActions,
|
||||
withViewDetails(),
|
||||
withPaymentMade(({ paymentMadeViews }) => ({
|
||||
paymentMadeViews,
|
||||
})),
|
||||
)(PaymentMadeViewTabs);
|
||||
@@ -0,0 +1,27 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { getResourceViews } from 'store/customViews/customViews.selectors';
|
||||
import {
|
||||
getPaymentMadeCurrentPageFactory,
|
||||
getPaymentMadePaginationMetaFactory,
|
||||
getPaymentMadeTableQuery,
|
||||
} from 'store/PaymentMades/paymentMade.selector';
|
||||
|
||||
|
||||
export default (mapState) => {
|
||||
const getPyamentMadesItems = getPaymentMadeCurrentPageFactory();
|
||||
const getPyamentMadesPaginationMeta = getPaymentMadePaginationMetaFactory();
|
||||
const mapStateToProps = (state, props) => {
|
||||
const query = getPaymentMadeTableQuery(state, props);
|
||||
const mapped = {
|
||||
paymentMadeCurrentPage: getPyamentMadesItems(state, props, query),
|
||||
paymentMadeViews: getResourceViews(state, props, 'bill_payments'),
|
||||
paymentMadeItems: state.paymentMades.items,
|
||||
paymentMadeTableQuery: query,
|
||||
paymentMadePageination: getPyamentMadesPaginationMeta(state, props, query),
|
||||
paymentMadesLoading: state.paymentMades.loading,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { connect } from 'react-redux';
|
||||
import t from 'store/types';
|
||||
import {
|
||||
submitPaymentMade,
|
||||
editPaymentMade,
|
||||
deletePaymentMade,
|
||||
fetchPaymentMadesTable,
|
||||
fetchPaymentMade,
|
||||
} from 'store/PaymentMades/paymentMade.actions';
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
requestSubmitPaymentMade: (form) => dispatch(submitPaymentMade({ form })),
|
||||
requestFetchPaymentMade: (id) => dispatch(fetchPaymentMade({ id })),
|
||||
requestEditPaymentMade: (id, form) => dispatch(editPaymentMade(id, form)),
|
||||
requestDeletePaymentMade: (id) => dispatch(deletePaymentMade({ id })),
|
||||
requestFetchPaymentMadesTable: (query = {}) =>
|
||||
dispatch(fetchPaymentMadesTable({ query: { ...query } })),
|
||||
|
||||
changePaymentMadeView: (id) =>
|
||||
dispatch({
|
||||
type: t.PAYMENT_MADE_SET_CURRENT_VIEW,
|
||||
currentViewId: parseInt(id, 10),
|
||||
}),
|
||||
|
||||
addPaymentMadesTableQueries: (queries) =>
|
||||
dispatch({
|
||||
type: t.PAYMENT_MADE_TABLE_QUERIES_ADD,
|
||||
queries,
|
||||
}),
|
||||
});
|
||||
export default connect(null, mapDispatchToProps);
|
||||
@@ -0,0 +1,11 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { getPaymentMadeByIdFactory } from 'store/PaymentMades/paymentMade.selector';
|
||||
|
||||
export default () => {
|
||||
const getPaymentMadeById = getPaymentMadeByIdFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
paymentMade: getPaymentMadeById(state, props),
|
||||
});
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
submitBill,
|
||||
deleteBill,
|
||||
editBill,
|
||||
fetchBillsTable,
|
||||
fetchBill,
|
||||
} from 'store/Bills/bills.actions';
|
||||
import t from 'store/types';
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
requestSubmitBill: (form) => dispatch(submitBill({ form })),
|
||||
requestFetchBill: (id) => dispatch(fetchBill({ id })),
|
||||
requestEditBill: (id, form) => dispatch(editBill({ id, form })),
|
||||
requestDeleteBill: (id) => dispatch(deleteBill({ id })),
|
||||
requestFetchBillsTable: (query = {}) =>
|
||||
dispatch(fetchBillsTable({ query: { ...query } })),
|
||||
|
||||
changeBillView: (id) =>
|
||||
dispatch({
|
||||
type: t.BILL_SET_CURRENT_VIEW,
|
||||
currentViewId: parseInt(id, 10),
|
||||
}),
|
||||
|
||||
addBillsTableQueries: (queries) =>
|
||||
dispatch({
|
||||
type: t.BILLS_TABLE_QUERIES_ADD,
|
||||
queries,
|
||||
}),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps);
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { omit } from 'lodash';
|
||||
import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
|
||||
import CLASSES from 'components/classes';
|
||||
import DataTable from 'components/DataTable';
|
||||
import Icon from 'components/Icon';
|
||||
|
||||
import { compose, formattedAmount } from 'utils';
|
||||
import {
|
||||
InputGroupCell,
|
||||
MoneyFieldCell,
|
||||
@@ -14,7 +15,7 @@ import {
|
||||
} from 'components/DataTableCells';
|
||||
|
||||
import withItems from 'containers/Items/withItems';
|
||||
import { omit } from 'lodash';
|
||||
import { compose, formattedAmount } from 'utils';
|
||||
|
||||
const ActionsCellRenderer = ({
|
||||
row: { index },
|
||||
@@ -92,6 +93,7 @@ function EstimateTable({
|
||||
width: 40,
|
||||
disableResizing: true,
|
||||
disableSortBy: true,
|
||||
className: 'index',
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'product_and_service' }),
|
||||
@@ -108,6 +110,7 @@ function EstimateTable({
|
||||
Cell: InputGroupCell,
|
||||
disableSortBy: true,
|
||||
className: 'description',
|
||||
width: 120,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -123,7 +126,7 @@ function EstimateTable({
|
||||
accessor: 'rate',
|
||||
Cell: TotalEstimateCellRederer(MoneyFieldCell, 'rate'),
|
||||
disableSortBy: true,
|
||||
width: 150,
|
||||
width: 100,
|
||||
className: 'rate',
|
||||
},
|
||||
{
|
||||
@@ -230,8 +233,9 @@ function EstimateTable({
|
||||
updateData: handleUpdateData,
|
||||
removeRow: handleRemoveRow,
|
||||
}}
|
||||
className={CLASSES.DATATABLE_EDITOR}
|
||||
/>
|
||||
<div className={'mt1'}>
|
||||
<div className={'datatable-editor-actions mt1'}>
|
||||
<Button
|
||||
small={true}
|
||||
className={'button--secondary button--new-line'}
|
||||
|
||||
@@ -153,7 +153,6 @@ function EstimateActionsBar({
|
||||
const mapStateToProps = (state, props) => ({
|
||||
resourceName: 'sales_estimates',
|
||||
});
|
||||
|
||||
const withEstimateActionsBar = connect(mapStateToProps);
|
||||
|
||||
export default compose(
|
||||
|
||||
@@ -5,14 +5,13 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import * as Yup from 'yup';
|
||||
import { useFormik } from 'formik';
|
||||
import moment from 'moment';
|
||||
import { Intent, FormGroup, TextArea, Button } from '@blueprintjs/core';
|
||||
import { Intent, FormGroup, TextArea } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import { pick, omit } from 'lodash';
|
||||
import { queryCache } from 'react-query';
|
||||
import { pick } from 'lodash';
|
||||
import { Row, Col } from 'react-grid-system';
|
||||
|
||||
import EstimateFormHeader from './EstimateFormHeader';
|
||||
import EstimatesItemsTable from './EntriesItemsTable';
|
||||
@@ -103,13 +102,11 @@ const EstimateForm = ({
|
||||
.min(1)
|
||||
.max(1024)
|
||||
.label(formatMessage({ id: 'note' })),
|
||||
|
||||
terms_conditions: Yup.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(1024)
|
||||
.label(formatMessage({ id: 'note' })),
|
||||
|
||||
entries: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
quantity: Yup.number().nullable(),
|
||||
@@ -176,7 +173,7 @@ const EstimateForm = ({
|
||||
index: index + 1,
|
||||
}));
|
||||
};
|
||||
// debugger;
|
||||
|
||||
const initialValues = useMemo(
|
||||
() => ({
|
||||
...(estimate
|
||||
@@ -200,14 +197,6 @@ const EstimateForm = ({
|
||||
[estimate, defaultInitialValues, defaultEstimate],
|
||||
);
|
||||
|
||||
// const initialValues = useMemo(
|
||||
// () => ({
|
||||
// ...defaultInitialValues,
|
||||
// entries: orderingProductsIndex(defaultInitialValues.entries),
|
||||
// }),
|
||||
// [defaultEstimate, defaultInitialValues, estimate],
|
||||
// );
|
||||
|
||||
const initialAttachmentFiles = useMemo(() => {
|
||||
return estimate && estimate.media
|
||||
? estimate.media.map((attach) => ({
|
||||
@@ -251,12 +240,8 @@ const EstimateForm = ({
|
||||
.then((response) => {
|
||||
AppToaster.show({
|
||||
message: formatMessage(
|
||||
{
|
||||
id: 'the_estimate_has_been_successfully_created',
|
||||
},
|
||||
{
|
||||
number: values.estimate_number,
|
||||
},
|
||||
{ id: 'the_estimate_has_been_successfully_created' },
|
||||
{ number: values.estimate_number },
|
||||
),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
@@ -270,7 +255,7 @@ const EstimateForm = ({
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
console.log(formik.errors ,'ERROR');
|
||||
const handleSubmitClick = useCallback(
|
||||
(payload) => {
|
||||
setPayload(payload);
|
||||
@@ -313,9 +298,6 @@ const EstimateForm = ({
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
@@ -325,36 +307,45 @@ const EstimateForm = ({
|
||||
onClickAddNewRow={handleClickAddNewRow}
|
||||
onClickClearAllLines={handleClearAllLines}
|
||||
formik={formik}
|
||||
// defaultRow={defaultEstimate}
|
||||
/>
|
||||
<FormGroup
|
||||
label={<T id={'customer_note'} />}
|
||||
className={'form-group--customer_note'}
|
||||
>
|
||||
<TextArea growVertically={true} {...formik.getFieldProps('note')} />
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={<T id={'terms_conditions'} />}
|
||||
className={'form-group--terms_conditions'}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('terms_conditions')}
|
||||
/>
|
||||
</FormGroup>
|
||||
<Dragzone
|
||||
initialFiles={initialAttachmentFiles}
|
||||
onDrop={handleDropFiles}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
hint={'Attachments: Maxiumum size: 20MB'}
|
||||
/>
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
<FormGroup
|
||||
label={<T id={'customer_note'} />}
|
||||
className={'form-group--customer_note'}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('note')}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={<T id={'terms_conditions'} />}
|
||||
className={'form-group--terms_conditions'}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('terms_conditions')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<Dragzone
|
||||
initialFiles={initialAttachmentFiles}
|
||||
onDrop={handleDropFiles}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
hint={'Attachments: Maxiumum size: 20MB'}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</form>
|
||||
<EstimateFormFooter
|
||||
formik={formik}
|
||||
onSubmitClick={handleSubmitClick}
|
||||
estimate={estimate}
|
||||
onCancelClick={handleCancelClick}
|
||||
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function EstimateFormFooter({
|
||||
estimate,
|
||||
}) {
|
||||
return (
|
||||
<div className={'estimate-form__floating-footer'}>
|
||||
<div className={'form__floating-footer'}>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
|
||||
@@ -68,13 +68,16 @@ function EstimateFormHeader({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={'estimate-form'}>
|
||||
<div className={'estimate-form__primary-section'}>
|
||||
{/* customer name */}
|
||||
<div className={'page-form page-form--estimate'}>
|
||||
<div className={'page-form__primary-section'}>
|
||||
<FormGroup
|
||||
label={<T id={'customer_name'} />}
|
||||
inline={true}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
className={classNames(
|
||||
'form-group--select-list',
|
||||
'form-group--customer',
|
||||
Classes.FILL,
|
||||
)}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
intent={errors.customer_id && touched.customer_id && Intent.DANGER}
|
||||
helperText={
|
||||
@@ -94,17 +97,18 @@ function EstimateFormHeader({
|
||||
labelProp={'display_name'}
|
||||
/>
|
||||
</FormGroup>
|
||||
{/* estimate_date */}
|
||||
<Row>
|
||||
<Col
|
||||
|
||||
// md={9} push={{ md: 3 }}
|
||||
>
|
||||
<Row>
|
||||
<Col>
|
||||
<FormGroup
|
||||
label={<T id={'estimate_date'} />}
|
||||
inline={true}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
className={classNames(
|
||||
'form-group--select-list',
|
||||
Classes.FILL,
|
||||
'form-group--estimate-date',
|
||||
)}
|
||||
intent={
|
||||
errors.estimate_date && touched.estimate_date && Intent.DANGER
|
||||
}
|
||||
@@ -120,14 +124,15 @@ function EstimateFormHeader({
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col
|
||||
|
||||
// md={3} pull={{ md: 9 }}
|
||||
>
|
||||
<Col>
|
||||
<FormGroup
|
||||
label={<T id={'expiration_date'} />}
|
||||
inline={true}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
className={classNames(
|
||||
'form-group--select-list',
|
||||
'form-group--expiration-date',
|
||||
Classes.FILL,
|
||||
)}
|
||||
intent={
|
||||
errors.expiration_date &&
|
||||
touched.expiration_date &&
|
||||
@@ -147,11 +152,12 @@ function EstimateFormHeader({
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
{/* Estimate */}
|
||||
|
||||
{/*- Estimate -*/}
|
||||
<FormGroup
|
||||
label={<T id={'estimate'} />}
|
||||
inline={true}
|
||||
className={('form-group--estimate', Classes.FILL)}
|
||||
className={('form-group--estimate-number', Classes.FILL)}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
intent={
|
||||
errors.estimate_number && touched.estimate_number && Intent.DANGER
|
||||
@@ -168,6 +174,7 @@ function EstimateFormHeader({
|
||||
{...getFieldProps('estimate_number')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={<T id={'reference'} />}
|
||||
inline={true}
|
||||
|
||||
@@ -33,6 +33,7 @@ function Estimates({
|
||||
requestFetchCustomers({}),
|
||||
);
|
||||
|
||||
//
|
||||
const handleFormSubmit = useCallback(
|
||||
(payload) => {
|
||||
payload.redirect && history.push('/estimates');
|
||||
|
||||
@@ -2,22 +2,25 @@ import { connect } from 'react-redux';
|
||||
import { getResourceViews } from 'store/customViews/customViews.selectors';
|
||||
import {
|
||||
getEstimateCurrentPageFactory,
|
||||
getEstimatesTableQuery,
|
||||
getEstimatesTableQueryFactory,
|
||||
getEstimatesPaginationMetaFactory,
|
||||
} from 'store/Estimate/estimates.selectors';
|
||||
|
||||
export default (mapState) => {
|
||||
const getEstimatesItems = getEstimateCurrentPageFactory();
|
||||
const getEstimatesPaginationMeta = getEstimatesPaginationMetaFactory();
|
||||
const getEstimatesTableQuery = getEstimatesTableQueryFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const query = getEstimatesTableQuery(state, props);
|
||||
|
||||
const mapped = {
|
||||
estimatesCurrentPage: getEstimatesItems(state, props, query),
|
||||
estimateViews: getResourceViews(state, props, 'sales_estimates'),
|
||||
estimateItems: state.sales_estimates.items,
|
||||
estimateItems: state.salesEstimates.items,
|
||||
estimateTableQuery: query,
|
||||
estimatesPageination: getEstimatesPaginationMeta(state, props, query),
|
||||
estimatesLoading: state.sales_estimates.loading,
|
||||
estimatesLoading: state.salesEstimates.loading,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import moment from 'moment';
|
||||
import { Intent, FormGroup, TextArea, Button } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import { pick } from 'lodash';
|
||||
import { Row, Col } from 'react-grid-system';
|
||||
|
||||
import InvoiceFormHeader from './InvoiceFormHeader';
|
||||
import EstimatesItemsTable from 'containers/Sales/Estimate/EntriesItemsTable';
|
||||
@@ -162,7 +163,7 @@ function InvoiceForm({
|
||||
index: index + 1,
|
||||
}));
|
||||
};
|
||||
// debugger;
|
||||
|
||||
const initialValues = useMemo(
|
||||
() => ({
|
||||
...(invoice
|
||||
@@ -186,14 +187,6 @@ function InvoiceForm({
|
||||
[invoice, defaultInitialValues, defaultInvoice],
|
||||
);
|
||||
|
||||
// const initialValues = useMemo(
|
||||
// () => ({
|
||||
// ...defaultInitialValues,
|
||||
// entries: orderingIndex(defaultInitialValues.entries),
|
||||
// }),
|
||||
// [defaultInvoice, defaultInitialValues, invoice],
|
||||
// );
|
||||
|
||||
const initialAttachmentFiles = useMemo(() => {
|
||||
return invoice && invoice.media
|
||||
? invoice.media.map((attach) => ({
|
||||
@@ -308,31 +301,39 @@ function InvoiceForm({
|
||||
onClickClearAllLines={handleClearAllLines}
|
||||
formik={formik}
|
||||
/>
|
||||
<FormGroup
|
||||
label={<T id={'invoice_message'} />}
|
||||
className={'form-group--customer_note'}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('invoice_message')}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={<T id={'terms_conditions'} />}
|
||||
className={'form-group--terms_conditions'}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('terms_conditions')}
|
||||
/>
|
||||
</FormGroup>
|
||||
<Dragzone
|
||||
initialFiles={initialAttachmentFiles}
|
||||
onDrop={handleDropFiles}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
hint={'Attachments: Maxiumum size: 20MB'}
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
<FormGroup
|
||||
label={<T id={'invoice_message'} />}
|
||||
className={'form-group--customer_note'}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('invoice_message')}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={<T id={'terms_conditions'} />}
|
||||
className={'form-group--terms_conditions'}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('terms_conditions')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<Dragzone
|
||||
initialFiles={initialAttachmentFiles}
|
||||
onDrop={handleDropFiles}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
hint={'Attachments: Maxiumum size: 20MB'}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</form>
|
||||
|
||||
<InvoiceFormFooter
|
||||
formik={formik}
|
||||
onSubmitClick={handleSubmitClick}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function EstimateFormFooter({
|
||||
invoice,
|
||||
}) {
|
||||
return (
|
||||
<div className={'estimate-form__floating-footer'}>
|
||||
<div className={'form__floating-footer'}>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
|
||||
@@ -68,8 +68,8 @@ function InvoiceFormHeader({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={'invoice-form'}>
|
||||
<div className={'invoice__primary-section'}>
|
||||
<div class="page-form page-form--invoice">
|
||||
<div className={'page-form__primary-section'}>
|
||||
{/* customer name */}
|
||||
<FormGroup
|
||||
label={<T id={'customer_name'} />}
|
||||
|
||||
@@ -19,6 +19,12 @@ function Invoices({
|
||||
const history = useHistory();
|
||||
const { id } = useParams();
|
||||
|
||||
const fetchInvoice = useQuery(
|
||||
['invoice', id],
|
||||
(key, _id) => requsetFetchInvoice(_id),
|
||||
{ enabled: !!id },
|
||||
);
|
||||
|
||||
// Handle fetch Items data table or list
|
||||
const fetchItems = useQuery('items-table', () => requestFetchItems({}));
|
||||
|
||||
@@ -33,12 +39,6 @@ function Invoices({
|
||||
requestFetchCustomers({}),
|
||||
);
|
||||
|
||||
const fetchInvoice = useQuery(
|
||||
['invoice', id],
|
||||
(key, _id) => requsetFetchInvoice(_id),
|
||||
{ enabled: !!id },
|
||||
);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
history.goBack();
|
||||
}, [history]);
|
||||
|
||||
@@ -5,17 +5,18 @@ import {
|
||||
deleteInvoice,
|
||||
fetchInvoice,
|
||||
fetchInvoicesTable,
|
||||
dueInvoices,
|
||||
} from 'store/Invoice/invoices.actions';
|
||||
import t from 'store/types';
|
||||
|
||||
const mapDipatchToProps = (dispatch) => ({
|
||||
requestSubmitInvoice: (form) => dispatch(submitInvoice({ form })),
|
||||
requsetFetchInvoice: (id) => dispatch(fetchInvoice({ id })),
|
||||
requestEditInvoice: (id, form) => dispatch(editInvoice( id, form )),
|
||||
requestEditInvoice: (id, form) => dispatch(editInvoice(id, form)),
|
||||
requestFetchInvoiceTable: (query = {}) =>
|
||||
dispatch(fetchInvoicesTable({ query: { ...query } })),
|
||||
requestDeleteInvoice: (id) => dispatch(deleteInvoice({ id })),
|
||||
|
||||
requestFetchDueInvoices: (id) => dispatch(dueInvoices({ id })),
|
||||
changeInvoiceView: (id) =>
|
||||
dispatch({
|
||||
type: t.INVOICES_SET_CURRENT_VIEW,
|
||||
|
||||
@@ -3,21 +3,28 @@ import { getResourceViews } from 'store/customViews/customViews.selectors';
|
||||
import {
|
||||
getInvoiceCurrentPageFactory,
|
||||
getInvoicePaginationMetaFactory,
|
||||
getInvoiceTableQueryFactory,
|
||||
getInvoiceTableQuery,
|
||||
getdueInvoices,
|
||||
} from 'store/Invoice/invoices.selector';
|
||||
|
||||
export default (mapState) => {
|
||||
const getInvoicesItems = getInvoiceCurrentPageFactory();
|
||||
|
||||
const getInvoicesPaginationMeta = getInvoicePaginationMetaFactory();
|
||||
const getInvoiceTableQuery = getInvoiceTableQueryFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const query = getInvoiceTableQuery(state, props);
|
||||
|
||||
const mapped = {
|
||||
invoicesCurrentPage: getInvoicesItems(state, props, query),
|
||||
invoicesViews: getResourceViews(state, props, 'sales_invoices'),
|
||||
invoicesItems: state.sales_invoices.items,
|
||||
invoicesItems: state.salesInvoices.items,
|
||||
invoicesTableQuery: query,
|
||||
invoicesPageination: getInvoicesPaginationMeta(state, props, query),
|
||||
invoicesLoading: state.sales_invoices.loading,
|
||||
invoicesLoading: state.salesInvoices.loading,
|
||||
dueInvoices: getdueInvoices(state, props),
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import React, { useCallback, useState, useMemo } from 'react';
|
||||
import Icon from 'components/Icon';
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Popover,
|
||||
NavbarDivider,
|
||||
NavbarGroup,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useRouteMatch, useHistory } from 'react-router-dom';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import FilterDropdown from 'components/FilterDropdown';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
|
||||
import { If, DashboardActionViewsList } from 'components';
|
||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
||||
|
||||
import withPaymentReceivesActions from './withPaymentReceivesActions';
|
||||
import withPaymentReceives from './withPaymentReceives';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
function PaymentReceiveActionsBar({
|
||||
// #withResourceDetail
|
||||
resourceFields,
|
||||
|
||||
//#withPaymentReceives
|
||||
paymentReceivesViews,
|
||||
|
||||
//#withPaymentReceivesActions
|
||||
addPaymentReceivesTableQueries,
|
||||
|
||||
// #own Porps
|
||||
onFilterChanged,
|
||||
selectedRows = [],
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { path } = useRouteMatch();
|
||||
const [filterCount, setFilterCount] = useState(0);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const handleClickNewPaymentReceive = useCallback(() => {
|
||||
history.push('/payment-receive/new');
|
||||
}, [history]);
|
||||
|
||||
// const filterDropdown = FilterDropdown({
|
||||
// initialCondition: {
|
||||
// fieldKey: '',
|
||||
// compatator: 'contains',
|
||||
// value: '',
|
||||
// },
|
||||
// fields: resourceFields,
|
||||
// onFilterChange: (filterConditions) => {
|
||||
// addPaymentReceivesTableQueries({
|
||||
// filter_roles: filterConditions || '',
|
||||
// });
|
||||
// onFilterChanged && onFilterChanged(filterConditions);
|
||||
// },
|
||||
// });
|
||||
|
||||
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
|
||||
selectedRows,
|
||||
]);
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<DashboardActionViewsList
|
||||
resourceName={'payment_receives'}
|
||||
views={paymentReceivesViews}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'plus'} />}
|
||||
text={<T id={'new_payment_receive'} />}
|
||||
onClick={handleClickNewPaymentReceive}
|
||||
/>
|
||||
<Popover
|
||||
minimal={true}
|
||||
// content={filterDropdown}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
>
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL)}
|
||||
text={
|
||||
filterCount <= 0 ? (
|
||||
<T id={'filter'} />
|
||||
) : (
|
||||
`${filterCount} ${formatMessage({ id: 'filters_applied' })}`
|
||||
)
|
||||
}
|
||||
icon={<Icon icon={'filter-16'} iconSize={16} />}
|
||||
/>
|
||||
</Popover>
|
||||
<If condition={hasSelectedRows}>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
// onClick={handleBulkDelete}
|
||||
/>
|
||||
</If>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'print-16'} iconSize={'16'} />}
|
||||
text={<T id={'print'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-import-16'} />}
|
||||
text={<T id={'import'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
resourceName: 'payment_receives',
|
||||
});
|
||||
const withPaymentReceiveActionsBar = connect(mapStateToProps);
|
||||
|
||||
export default compose(
|
||||
withPaymentReceiveActionsBar,
|
||||
withResourceDetail(({ resourceFields }) => ({
|
||||
resourceFields,
|
||||
})),
|
||||
withPaymentReceives(({ paymentReceivesViews }) => ({ paymentReceivesViews })),
|
||||
)(PaymentReceiveActionsBar);
|
||||
@@ -1,51 +1,65 @@
|
||||
import React, { useMemo, useCallback, useEffect, useState,useRef } from 'react';
|
||||
import React, {
|
||||
useMemo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
useRef,
|
||||
} from 'react';
|
||||
|
||||
import * as Yup from 'yup';
|
||||
import { useFormik } from 'formik';
|
||||
import moment from 'moment';
|
||||
import { Intent, FormGroup, TextArea } from '@blueprintjs/core';
|
||||
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import { pick, values } from 'lodash';
|
||||
|
||||
import PaymentReceiveHeader from './PaymentReceiveFormHeader';
|
||||
// PaymentReceiptItemsTable
|
||||
import PaymentReceiveItemsTable from './PaymentReceiveItemsTable';
|
||||
import PaymentReceiveFooter from './PaymentReceiveFormFooter';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withMediaActions from 'containers/Media/withMediaActions';
|
||||
import withPaymentReceivesActions from './withPaymentReceivesActions'
|
||||
|
||||
import withPaymentReceivesActions from './withPaymentReceivesActions';
|
||||
import withInvoices from '../Invoice/withInvoices';
|
||||
import withPaymentReceiveDetail from './withPaymentReceiveDetail';
|
||||
import withPaymentReceives from './withPaymentReceives';
|
||||
import { AppToaster } from 'components';
|
||||
import Dragzone from 'components/Dragzone';
|
||||
import useMedia from 'hooks/useMedia';
|
||||
|
||||
import { compose, repeatValue } from 'utils';
|
||||
|
||||
const MIN_LINES_NUMBER = 4;
|
||||
const MIN_LINES_NUMBER = 5;
|
||||
|
||||
function PaymentReceiveForm({
|
||||
//#withMedia
|
||||
requestSubmitMedia,
|
||||
requestDeleteMedia,
|
||||
|
||||
|
||||
//#WithPaymentReceiveActions
|
||||
requestSubmitPaymentReceive,
|
||||
|
||||
requestEditPaymentReceive,
|
||||
|
||||
//#withDashboard
|
||||
changePageTitle,
|
||||
changePageSubtitle,
|
||||
|
||||
//#withPaymentReceiveDetail
|
||||
paymentReceive,
|
||||
paymentReceiveInvoices,
|
||||
paymentReceivesItems,
|
||||
|
||||
//#OWn Props
|
||||
payment_receive,
|
||||
// payment_receive,
|
||||
onFormSubmit,
|
||||
onCancelForm,
|
||||
dueInvoiceLength,
|
||||
onCustomerChange,
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
const [payload, setPayload] = useState({});
|
||||
|
||||
const { id } = useParams();
|
||||
const {
|
||||
setFiles,
|
||||
saveMedia,
|
||||
@@ -63,43 +77,48 @@ function PaymentReceiveForm({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (payment_receive && payment_receive.id) {
|
||||
onCustomerChange && onCustomerChange(formik.values.customer_id);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (paymentReceive && paymentReceive.id) {
|
||||
changePageTitle(formatMessage({ id: 'edit_payment_receive' }));
|
||||
} else {
|
||||
changePageTitle(formatMessage({ id: 'new_payment_receive' }));
|
||||
changePageTitle(formatMessage({ id: 'payment_receive' }));
|
||||
}
|
||||
}, [changePageTitle, payment_receive, formatMessage]);
|
||||
}, [changePageTitle, paymentReceive, formatMessage]);
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
customer_id: Yup.string()
|
||||
.label(formatMessage({ id: 'customer_name_' }))
|
||||
.required(),
|
||||
deposit_account_id: Yup.number()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'deposit_account_' })),
|
||||
payment_date: Yup.date()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'payment_date_' })),
|
||||
deposit_account_id: Yup.number()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'deposit_account_' })),
|
||||
// receive_amount: Yup.number()
|
||||
// .required()
|
||||
// .label(formatMessage({ id: 'receive_amount_' })),
|
||||
payment_receive_no: Yup.number()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'payment_receive_no_' })),
|
||||
reference_no: Yup.string().min(1).max(255),
|
||||
statement: Yup.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(1024)
|
||||
.label(formatMessage({ id: 'statement' })),
|
||||
|
||||
reference_no: Yup.string().min(1).max(255).nullable(),
|
||||
description: Yup.string().nullable(),
|
||||
entries: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
payment_amount: Yup.number().nullable(),
|
||||
item_id: Yup.number()
|
||||
invoice_no: Yup.number().nullable(),
|
||||
balance: Yup.number().nullable(),
|
||||
due_amount: Yup.number().nullable(),
|
||||
invoice_date: Yup.date(),
|
||||
invoice_id: Yup.number()
|
||||
.nullable()
|
||||
.when(['payment_amount'], {
|
||||
is: (payment_amount) => payment_amount,
|
||||
then: Yup.number().required(),
|
||||
}),
|
||||
description: Yup.string().nullable(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
@@ -114,9 +133,12 @@ function PaymentReceiveForm({
|
||||
|
||||
const defaultPaymentReceive = useMemo(
|
||||
() => ({
|
||||
item_id: null,
|
||||
payment_amount: null,
|
||||
description: null,
|
||||
invoice_id: '',
|
||||
invoice_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
invoice_no: '',
|
||||
balance: '',
|
||||
due_amount: '',
|
||||
payment_amount: '',
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@@ -126,29 +148,53 @@ function PaymentReceiveForm({
|
||||
deposit_account_id: '',
|
||||
payment_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
reference_no: '',
|
||||
statement: '',
|
||||
payment_receive_no: '',
|
||||
// receive_amount: '',
|
||||
description: '',
|
||||
entries: [...repeatValue(defaultPaymentReceive, MIN_LINES_NUMBER)],
|
||||
}),
|
||||
[defaultPaymentReceive],
|
||||
);
|
||||
|
||||
const orderingIndex = (_entries) => {
|
||||
return _entries.map((item, index) => ({
|
||||
...item,
|
||||
index: index + 1,
|
||||
}));
|
||||
};
|
||||
|
||||
const initialValues = useMemo(
|
||||
() => ({
|
||||
...defaultInitialValues,
|
||||
entries: defaultInitialValues.entries,
|
||||
...(paymentReceive
|
||||
? {
|
||||
...pick(paymentReceive, Object.keys(defaultInitialValues)),
|
||||
entries: [
|
||||
...paymentReceive.entries.map((paymentReceive) => ({
|
||||
...pick(paymentReceive, Object.keys(defaultPaymentReceive)),
|
||||
})),
|
||||
...repeatValue(
|
||||
defaultPaymentReceive,
|
||||
Math.max(MIN_LINES_NUMBER - paymentReceive.entries.length, 0),
|
||||
),
|
||||
],
|
||||
}
|
||||
: {
|
||||
...defaultInitialValues,
|
||||
entries: orderingIndex(defaultInitialValues.entries),
|
||||
}),
|
||||
}),
|
||||
[defaultPaymentReceive, defaultInitialValues, payment_receive],
|
||||
[paymentReceive, defaultInitialValues, defaultPaymentReceive],
|
||||
);
|
||||
|
||||
const initialAttachmentFiles = useMemo(() => {
|
||||
return payment_receive && payment_receive.media
|
||||
? payment_receive.media.map((attach) => ({
|
||||
return paymentReceive && paymentReceive.media
|
||||
? paymentReceive.media.map((attach) => ({
|
||||
preview: attach.attachment_file,
|
||||
uploaded: true,
|
||||
metadata: { ...attach },
|
||||
}))
|
||||
: [];
|
||||
}, [payment_receive]);
|
||||
}, [paymentReceive]);
|
||||
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
@@ -157,38 +203,52 @@ function PaymentReceiveForm({
|
||||
...initialValues,
|
||||
},
|
||||
onSubmit: async (values, { setSubmitting, setErrors, resetForm }) => {
|
||||
setSubmitting(true);
|
||||
const entries = formik.values.entries.filter((item) => {
|
||||
if (item.invoice_id !== undefined) {
|
||||
return { ...item };
|
||||
}
|
||||
});
|
||||
const form = {
|
||||
...values,
|
||||
entries,
|
||||
};
|
||||
const savePaymentReceive = (mediaIds) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const requestForm = { ...form, media_ids: mediaIds };
|
||||
|
||||
requestSubmitPaymentReceive(requestForm)
|
||||
.the((response) => {
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'the_payment_receive_has_been_successfully_created',
|
||||
}),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
setSubmitting(false);
|
||||
clearSavedMediaIds();
|
||||
savePaymentReceiveSubmit({ action: 'new', ...payload });
|
||||
})
|
||||
.catch((errors) => {
|
||||
setSubmitting(false);
|
||||
const requestForm = { ...form };
|
||||
|
||||
if (paymentReceive && paymentReceive.id) {
|
||||
requestEditPaymentReceive(paymentReceive.id, requestForm)
|
||||
.then((response) => {
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'the_payment_receive_has_been_successfully_edited',
|
||||
}),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
});
|
||||
Promise.all([saveMedia(), deleteMedia()])
|
||||
.then(([savedMediaResponses]) => {
|
||||
const mediaIds = savedMediaResponses.map((res) => res.data.media.id);
|
||||
savedMediaIds.current = mediaIds;
|
||||
return savedMediaResponses;
|
||||
})
|
||||
.then(() => {
|
||||
return savePaymentReceive(savedMediaIds.current);
|
||||
});
|
||||
setSubmitting(false);
|
||||
savePaymentReceiveSubmit({ action: 'update', ...payload });
|
||||
resetForm();
|
||||
})
|
||||
.catch((error) => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
} else {
|
||||
requestSubmitPaymentReceive(requestForm)
|
||||
.then((response) => {
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'the_payment_receive_has_been_successfully_created',
|
||||
}),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
setSubmitting(false);
|
||||
resetForm();
|
||||
savePaymentReceiveSubmit({ action: 'new', ...payload });
|
||||
})
|
||||
.catch((errors) => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -222,33 +282,47 @@ function PaymentReceiveForm({
|
||||
formik.resetForm();
|
||||
};
|
||||
|
||||
const handleClickAddNewRow = () => {
|
||||
formik.setFieldValue(
|
||||
'entries',
|
||||
orderingIndex([...formik.values.entries, defaultPaymentReceive]),
|
||||
);
|
||||
};
|
||||
|
||||
const handleClearAllLines = () => {
|
||||
formik.setFieldValue(
|
||||
'entries',
|
||||
orderingIndex([...repeatValue(defaultPaymentReceive, MIN_LINES_NUMBER)]),
|
||||
);
|
||||
};
|
||||
console.log(formik.errors, 'ERROR');
|
||||
return (
|
||||
<div className={'payment_receive_form'}>
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<PaymentReceiveHeader formik={formik} />
|
||||
<FormGroup
|
||||
label={<T id={'statement'} />}
|
||||
className={'form-group--statement'}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('statement')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<Dragzone
|
||||
<PaymentReceiveItemsTable
|
||||
entries={formik.values.entries}
|
||||
customer_id={formik.values.customer_id}
|
||||
onClickAddNewRow={handleClickAddNewRow}
|
||||
onClickClearAllLines={handleClearAllLines}
|
||||
formik={formik}
|
||||
invoices={paymentReceiveInvoices}
|
||||
/>
|
||||
{/* <Dragzone
|
||||
initialFiles={initialAttachmentFiles}
|
||||
onDrop={handleDropFiles}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
hint={'Attachments: Maxiumum size: 20MB'}
|
||||
/>
|
||||
<PaymentReceiveFooter
|
||||
formik={formik}
|
||||
onSubmit={handleSubmitClick}
|
||||
onCancel={handleCancelClick}
|
||||
onClearClick={handleClearClick}
|
||||
/>
|
||||
/> */}
|
||||
</form>
|
||||
|
||||
<PaymentReceiveFooter
|
||||
formik={formik}
|
||||
onSubmitClick={handleSubmitClick}
|
||||
paymentReceive={paymentReceive}
|
||||
onCancel={handleCancelClick}
|
||||
onClearClick={handleClearClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -257,4 +331,8 @@ export default compose(
|
||||
withPaymentReceivesActions,
|
||||
withDashboardActions,
|
||||
withMediaActions,
|
||||
withPaymentReceives(({ paymentReceivesItems }) => ({
|
||||
paymentReceivesItems,
|
||||
})),
|
||||
withPaymentReceiveDetail(),
|
||||
)(PaymentReceiveForm);
|
||||
|
||||
@@ -7,11 +7,23 @@ export default function PaymentReceiveFormFooter({
|
||||
onSubmitClick,
|
||||
onCancelClick,
|
||||
onClearClick,
|
||||
paymentReceive,
|
||||
}) {
|
||||
return (
|
||||
<div className={'estimate-form__floating-footer'}>
|
||||
<Button disabled={isSubmitting} intent={Intent.PRIMARY} type="submit">
|
||||
<T id={'save_send'} />
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
onSubmitClick({ redirect: true });
|
||||
}}
|
||||
>
|
||||
{paymentReceive && paymentReceive.id ? (
|
||||
<T id={'edit'} />
|
||||
) : (
|
||||
<T id={'save_send'} />
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -20,6 +32,9 @@ export default function PaymentReceiveFormFooter({
|
||||
className={'ml1'}
|
||||
name={'save'}
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
onSubmitClick({ redirect: false });
|
||||
}}
|
||||
>
|
||||
<T id={'save'} />
|
||||
</Button>
|
||||
|
||||
@@ -10,6 +10,9 @@ import {
|
||||
|
||||
import { DateInput } from '@blueprintjs/datetime';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import moment from 'moment';
|
||||
import { momentFormatter, compose, tansformDateValue } from 'utils';
|
||||
import classNames from 'classnames';
|
||||
@@ -106,8 +109,55 @@ function PaymentReceiveFormHeader({
|
||||
labelProp={'display_name'}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{/* Payment date */}
|
||||
<FormGroup
|
||||
label={<T id={'deposit_account'} />}
|
||||
label={<T id={'payment_date'} />}
|
||||
inline={true}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
intent={errors.payment_date && touched.payment_date && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage name="payment_date" {...{ errors, touched }} />
|
||||
}
|
||||
>
|
||||
<DateInput
|
||||
{...momentFormatter('YYYY/MM/DD')}
|
||||
value={tansformDateValue(values.payment_date)}
|
||||
onChange={handleDateChange('payment_date')}
|
||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{/* payment receive no */}
|
||||
<FormGroup
|
||||
label={<T id={'payment_receive_no'} />}
|
||||
inline={true}
|
||||
className={('form-group--payment_receive_no', Classes.FILL)}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
intent={
|
||||
errors.payment_receive_no &&
|
||||
touched.payment_receive_no &&
|
||||
Intent.DANGER
|
||||
}
|
||||
helperText={
|
||||
<ErrorMessage name="payment_receive_no" {...{ errors, touched }} />
|
||||
}
|
||||
>
|
||||
<InputGroup
|
||||
intent={
|
||||
errors.payment_receive_no &&
|
||||
touched.payment_receive_no &&
|
||||
Intent.DANGER
|
||||
}
|
||||
minimal={true}
|
||||
{...getFieldProps('payment_receive_no')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{/* deposit account */}
|
||||
<FormGroup
|
||||
label={<T id={'deposit_to'} />}
|
||||
className={classNames(
|
||||
'form-group--deposit_account_id',
|
||||
'form-group--select-list',
|
||||
@@ -129,53 +179,37 @@ function PaymentReceiveFormHeader({
|
||||
>
|
||||
<AccountsSelectList
|
||||
accounts={depositAccounts}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
onAccountSelected={onChangeSelect('deposit_account_id')}
|
||||
defaultSelectText={<T id={'select_deposit_account'} />}
|
||||
selectedAccountId={values.deposit_account_id}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={<T id={'payment_date'} />}
|
||||
inline={true}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
intent={errors.payment_date && touched.payment_date && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage name="payment_date" {...{ errors, touched }} />
|
||||
}
|
||||
>
|
||||
<DateInput
|
||||
{...momentFormatter('YYYY/MM/DD')}
|
||||
value={tansformDateValue(values.payment_date)}
|
||||
onChange={handleDateChange}
|
||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
{/* payment receive no */}
|
||||
<FormGroup
|
||||
label={<T id={'payment_receive_no'} />}
|
||||
|
||||
{/* Receive amount */}
|
||||
|
||||
{/* <FormGroup
|
||||
label={<T id={'receive_amount'} />}
|
||||
inline={true}
|
||||
className={('form-group--payment_receive_no', Classes.FILL)}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
className={classNames('form-group--', Classes.FILL)}
|
||||
intent={
|
||||
errors.payment_receive_no &&
|
||||
touched.payment_receive_no &&
|
||||
Intent.DANGER
|
||||
errors.receive_amount && touched.receive_amount && Intent.DANGER
|
||||
}
|
||||
helperText={
|
||||
<ErrorMessage name="payment_receive_no" {...{ errors, touched }} />
|
||||
<ErrorMessage name="receive_amount" {...{ errors, touched }} />
|
||||
}
|
||||
>
|
||||
<InputGroup
|
||||
intent={
|
||||
errors.payment_receive_no &&
|
||||
touched.payment_receive_no &&
|
||||
Intent.DANGER
|
||||
errors.receive_amount && touched.receive_amount && Intent.DANGER
|
||||
}
|
||||
minimal={true}
|
||||
{...getFieldProps('payment_receive_no')}
|
||||
{...getFieldProps('receive_amount')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormGroup> */}
|
||||
|
||||
{/* reference_no */}
|
||||
<FormGroup
|
||||
label={<T id={'reference'} />}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import DataTable from 'components/DataTable';
|
||||
import Icon from 'components/Icon';
|
||||
import { Icon, DataTable } from 'components';
|
||||
import moment from 'moment';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
|
||||
import { compose, formattedAmount, transformUpdatedRows } from 'utils';
|
||||
import {
|
||||
@@ -10,4 +12,257 @@ import {
|
||||
MoneyFieldCell,
|
||||
EstimatesListFieldCell,
|
||||
DivFieldCell,
|
||||
EmptyDiv,
|
||||
} from 'components/DataTableCells';
|
||||
import withInvoices from '../Invoice/withInvoices';
|
||||
import withInvoiceActions from '../Invoice/withInvoiceActions';
|
||||
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
|
||||
import { omit, pick } from 'lodash';
|
||||
|
||||
const ActionsCellRenderer = ({
|
||||
row: { index },
|
||||
column: { id },
|
||||
cell: { value },
|
||||
data,
|
||||
payload,
|
||||
}) => {
|
||||
if (data.length <= index + 1) {
|
||||
return '';
|
||||
}
|
||||
const onRemoveRole = () => {
|
||||
payload.removeRow(index);
|
||||
};
|
||||
return (
|
||||
<Tooltip content={<T id={'remove_the_line'} />} position={Position.LEFT}>
|
||||
<Button
|
||||
icon={<Icon icon={'times-circle'} iconSize={14} />}
|
||||
iconSize={14}
|
||||
className="m12"
|
||||
intent={Intent.DANGER}
|
||||
onClick={onRemoveRole}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const CellRenderer = (content, type) => (props) => {
|
||||
if (props.data.length === props.row.index + 1) {
|
||||
return '';
|
||||
}
|
||||
return content(props);
|
||||
};
|
||||
|
||||
const TotalCellRederer = (content, type) => (props) => {
|
||||
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;
|
||||
|
||||
return computed;
|
||||
}, 0);
|
||||
return <span>{formattedAmount(total, 'USD')}</span>;
|
||||
}
|
||||
return content(props);
|
||||
};
|
||||
|
||||
function PaymentReceiveItemsTable({
|
||||
//#ownProps
|
||||
onClickRemoveRow,
|
||||
onClickAddNewRow,
|
||||
onClickClearAllLines,
|
||||
entries,
|
||||
formik: { errors, setFieldValue, values },
|
||||
|
||||
dueInvoices,
|
||||
customer_id,
|
||||
invoices,
|
||||
}) {
|
||||
const [rows, setRows] = useState([]);
|
||||
const [entrie, setEntrie] = useState([]);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
useEffect(() => {
|
||||
setRows([...dueInvoices.map((e) => ({ ...e })), ...invoices, {}]);
|
||||
setEntrie([
|
||||
...dueInvoices.map((e) => {
|
||||
return { id: e.id, payment_amount: e.payment_amount };
|
||||
}),
|
||||
]);
|
||||
}, [dueInvoices]);
|
||||
|
||||
console.log(rows, 'rows');
|
||||
console.log(entrie, 'entrie');
|
||||
console.log(values, 'values');
|
||||
// useEffect(() => {
|
||||
// setRows([...dueInvoices.map((e) => ({ ...e })), {}]);
|
||||
|
||||
// setEntrie([
|
||||
// ...dueInvoices.map((e) => {
|
||||
// return { id: e.id, payment_amount: e.payment_amount };
|
||||
// }),
|
||||
// ]);
|
||||
// }, [dueInvoices]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: '#',
|
||||
accessor: 'index',
|
||||
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
|
||||
width: 40,
|
||||
disableResizing: true,
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'Date' }),
|
||||
id: 'invoice_date',
|
||||
accessor: (r) => moment(r.invoice_date).format('YYYY MMM DD'),
|
||||
Cell: CellRenderer(EmptyDiv, 'invoice_date'),
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 250,
|
||||
},
|
||||
|
||||
{
|
||||
Header: formatMessage({ id: 'invocie_number' }),
|
||||
accessor: (row) => `#${row.invoice_no}`,
|
||||
Cell: CellRenderer(EmptyDiv, 'invoice_no'),
|
||||
disableSortBy: true,
|
||||
className: '',
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'invoice_amount' }),
|
||||
accessor: 'balance',
|
||||
Cell: CellRenderer(DivFieldCell, 'balance'),
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
className: '',
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'amount_due' }),
|
||||
accessor: 'due_amount',
|
||||
Cell: TotalCellRederer(DivFieldCell, 'due_amount'),
|
||||
disableSortBy: true,
|
||||
width: 150,
|
||||
className: '',
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'payment_amount' }),
|
||||
accessor: 'payment_amount',
|
||||
Cell: TotalCellRederer(MoneyFieldCell, 'payment_amount'),
|
||||
|
||||
disableSortBy: true,
|
||||
width: 150,
|
||||
className: '',
|
||||
},
|
||||
{
|
||||
Header: '',
|
||||
accessor: 'action',
|
||||
Cell: ActionsCellRenderer,
|
||||
className: 'actions',
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 45,
|
||||
},
|
||||
],
|
||||
[formatMessage],
|
||||
);
|
||||
|
||||
const handleRemoveRow = useCallback(
|
||||
(rowIndex) => {
|
||||
if (rows.length <= 1) {
|
||||
return;
|
||||
}
|
||||
const removeIndex = parseInt(rowIndex, 10);
|
||||
const newRows = rows.filter((row, index) => index !== removeIndex);
|
||||
|
||||
setFieldValue(
|
||||
'entries',
|
||||
newRows.map((row, index) => ({
|
||||
...omit(row),
|
||||
index: index - 1,
|
||||
})),
|
||||
);
|
||||
onClickRemoveRow && onClickRemoveRow(removeIndex);
|
||||
},
|
||||
[entrie, setFieldValue, onClickRemoveRow],
|
||||
);
|
||||
|
||||
const onClickNewRow = () => {
|
||||
onClickAddNewRow && onClickAddNewRow();
|
||||
};
|
||||
|
||||
const handleClickClearAllLines = () => {
|
||||
onClickClearAllLines && onClickClearAllLines();
|
||||
};
|
||||
|
||||
const rowClassNames = useCallback((row) => {
|
||||
return { 'row--total': rows.length === row.index + 1 };
|
||||
});
|
||||
|
||||
const handleUpdateData = useCallback(
|
||||
(rowIndex, columnId, value) => {
|
||||
const newRows = rows.map((row, index) => {
|
||||
if (index === rowIndex) {
|
||||
return {
|
||||
...rows[rowIndex],
|
||||
[columnId]: value,
|
||||
};
|
||||
}
|
||||
return row;
|
||||
});
|
||||
setRows(newRows);
|
||||
// setEntrie(newRows);
|
||||
setFieldValue(
|
||||
'entries',
|
||||
newRows.map((row) => ({
|
||||
...pick(row, ['payment_amount']),
|
||||
invoice_id: row.id,
|
||||
})),
|
||||
);
|
||||
},
|
||||
[rows, setFieldValue, setEntrie,setRows],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={'estimate-form__table'}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={rows}
|
||||
rowClassNames={rowClassNames}
|
||||
spinnerProps={false}
|
||||
payload={{
|
||||
errors: errors.entries || [],
|
||||
updateData: handleUpdateData,
|
||||
removeRow: handleRemoveRow,
|
||||
}}
|
||||
/>
|
||||
<div className={'mt1'}>
|
||||
<Button
|
||||
small={true}
|
||||
className={'button--secondary button--new-line'}
|
||||
onClick={onClickNewRow}
|
||||
>
|
||||
<T id={'new_lines'} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
small={true}
|
||||
className={'button--secondary button--clear-lines ml1'}
|
||||
onClick={handleClickClearAllLines}
|
||||
>
|
||||
<T id={'clear_all_lines'} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withInvoices(({ dueInvoices }) => ({
|
||||
dueInvoices,
|
||||
})),
|
||||
)(PaymentReceiveItemsTable);
|
||||
|
||||
182
client/src/containers/Sales/PaymentReceive/PaymentReceiveList.js
Normal file
182
client/src/containers/Sales/PaymentReceive/PaymentReceiveList.js
Normal file
@@ -0,0 +1,182 @@
|
||||
import React, { useEffect, useCallback, useMemo, useState } from 'react';
|
||||
import { Route, Switch, useHistory } from 'react-router-dom';
|
||||
import { useQuery, queryCache } from 'react-query';
|
||||
import { Alert, Intent } from '@blueprintjs/core';
|
||||
|
||||
import AppToaster from 'components/AppToaster';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
|
||||
import PaymentReceivesDataTable from './PaymentReceivesDataTable';
|
||||
import PaymentReceiveActionsBar from './PaymentReceiveActionsBar';
|
||||
import PaymentReceiveViewTabs from './PaymentReceiveViewTabs';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withResourceActions from 'containers/Resources/withResourcesActions';
|
||||
import withPaymentReceives from './withPaymentReceives';
|
||||
import withPaymentReceivesActions from './withPaymentReceivesActions';
|
||||
import withViewsActions from 'containers/Views/withViewsActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
function PaymentReceiveList({
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
|
||||
// #withViewsActions
|
||||
requestFetchResourceViews,
|
||||
requestFetchResourceFields,
|
||||
|
||||
//#withPaymentReceives
|
||||
paymentReceivesTableQuery,
|
||||
|
||||
//#withPaymentReceivesActions
|
||||
requestFetchPaymentReceiveTable,
|
||||
requestDeletePaymentReceive,
|
||||
addPaymentReceivesTableQueries,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { formatMessage } = useIntl();
|
||||
const [deletePaymentReceive, setDeletePaymentReceive] = useState(false);
|
||||
const [selectedRows, setSelectedRows] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
changePageTitle(formatMessage({ id: 'payment_Receive_list' }));
|
||||
}, [changePageTitle, formatMessage]);
|
||||
|
||||
const fetchResourceViews = useQuery(
|
||||
['resource-views', 'payment_receives'],
|
||||
(key, resourceName) => requestFetchResourceViews(resourceName),
|
||||
);
|
||||
|
||||
const fetchResourceFields = useQuery(
|
||||
['resource-fields', 'payment_receives'],
|
||||
(key, resourceName) => requestFetchResourceFields(resourceName),
|
||||
);
|
||||
|
||||
const fetchPaymentReceives = useQuery(
|
||||
['paymantReceives-table', paymentReceivesTableQuery],
|
||||
() => requestFetchPaymentReceiveTable(),
|
||||
);
|
||||
|
||||
//handle dalete Payment Receive
|
||||
const handleDeletePaymentReceive = useCallback(
|
||||
(paymentReceive) => {
|
||||
setDeletePaymentReceive(paymentReceive);
|
||||
},
|
||||
[setDeletePaymentReceive],
|
||||
);
|
||||
|
||||
// handle cancel Payment Receive
|
||||
const handleCancelPaymentReceiveDelete = useCallback(() => {
|
||||
setDeletePaymentReceive(false);
|
||||
}, [setDeletePaymentReceive]);
|
||||
|
||||
// handleConfirm delete payment receive
|
||||
const handleConfirmPaymentReceiveDelete = useCallback(() => {
|
||||
requestDeletePaymentReceive(deletePaymentReceive.id).then(() => {
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'the_payment_receive_has_been_successfully_deleted',
|
||||
}),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
setDeletePaymentReceive(false);
|
||||
});
|
||||
}, [deletePaymentReceive, requestDeletePaymentReceive, formatMessage]);
|
||||
|
||||
const handleEditPaymentReceive = useCallback((payment) => {
|
||||
history.push(`/payment-receive/${payment.id}/edit`);
|
||||
});
|
||||
|
||||
// Calculates the selected rows count.
|
||||
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
|
||||
selectedRows,
|
||||
]);
|
||||
|
||||
const handleFetchData = useCallback(
|
||||
({ pageIndex, pageSize, sortBy }) => {
|
||||
const page = pageIndex + 1;
|
||||
|
||||
addPaymentReceivesTableQueries({
|
||||
...(sortBy.length > 0
|
||||
? {
|
||||
column_sort_by: sortBy[0].id,
|
||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
||||
}
|
||||
: {}),
|
||||
page_size: pageSize,
|
||||
page,
|
||||
});
|
||||
},
|
||||
[addPaymentReceivesTableQueries],
|
||||
);
|
||||
|
||||
// Handle filter change to re-fetch data-table.
|
||||
const handleFilterChanged = useCallback(() => {}, [fetchPaymentReceives]);
|
||||
|
||||
// Handle selected rows change.
|
||||
const handleSelectedRowsChange = useCallback(
|
||||
(_payment) => {
|
||||
setSelectedRows(_payment);
|
||||
},
|
||||
[setSelectedRows],
|
||||
);
|
||||
|
||||
return (
|
||||
<DashboardInsider
|
||||
// loading={fetchResourceViews.isFetching || fetchResourceFields.isFetching}
|
||||
name={'payment_receives'}
|
||||
>
|
||||
<PaymentReceiveActionsBar
|
||||
// onBulkDelete={}
|
||||
selectedRows={selectedRows}
|
||||
onFilterChanged={handleFilterChanged}
|
||||
/>
|
||||
<DashboardPageContent>
|
||||
<Switch>
|
||||
<Route
|
||||
exact={true}
|
||||
path={[
|
||||
'/payment-receives/:custom_view_id/custom_view',
|
||||
'/payment-receives',
|
||||
]}
|
||||
>
|
||||
<PaymentReceiveViewTabs />
|
||||
<PaymentReceivesDataTable
|
||||
loading={fetchPaymentReceives.isFetching}
|
||||
onDeletePaymentReceive={handleDeletePaymentReceive}
|
||||
onFetchData={handleFetchData}
|
||||
onEditPaymentReceive={handleEditPaymentReceive}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
/>
|
||||
</Route>
|
||||
</Switch>
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={<T id={'delete'} />}
|
||||
icon={'trash'}
|
||||
intent={Intent.DANGER}
|
||||
isOpen={deletePaymentReceive}
|
||||
onCancel={handleCancelPaymentReceiveDelete}
|
||||
onConfirm={handleConfirmPaymentReceiveDelete}
|
||||
>
|
||||
<p>
|
||||
<T id={'once_delete_this_payment_receive_you_will_able_to_restore_it'} />
|
||||
</p>
|
||||
</Alert>
|
||||
</DashboardPageContent>
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withResourceActions,
|
||||
withPaymentReceivesActions,
|
||||
withDashboardActions,
|
||||
withViewsActions,
|
||||
withPaymentReceives(({ paymentReceivesTableQuery }) => ({
|
||||
paymentReceivesTableQuery,
|
||||
})),
|
||||
)(PaymentReceiveList);
|
||||
@@ -0,0 +1,110 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
|
||||
import { useParams, withRouter } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { pick, debounce } from 'lodash';
|
||||
|
||||
import { DashboardViewsTabs } from 'components';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
|
||||
import withPaymentReceives from './withPaymentReceives';
|
||||
import withPaymentReceivesActions from './withPaymentReceivesActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withViewDetails from 'containers/Views/withViewDetails';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
function PaymentReceiveViewTabs({
|
||||
//#withPaymentReceives
|
||||
paymentReceivesViews,
|
||||
|
||||
//#withPaymentReceivesActions
|
||||
changePaymentReceiveView,
|
||||
addPaymentReceivesTableQueries,
|
||||
|
||||
// #withViewDetails
|
||||
viewItem,
|
||||
|
||||
// #withDashboardActions
|
||||
setTopbarEditView,
|
||||
changePageSubtitle,
|
||||
|
||||
//#Own Props
|
||||
customViewChanged,
|
||||
onViewChanged,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { custom_view_id: customViewId = null } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
changePaymentReceiveView(customViewId || -1);
|
||||
setTopbarEditView(customViewId);
|
||||
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
|
||||
|
||||
addPaymentReceivesTableQueries({
|
||||
custom_view_id: customViewId,
|
||||
});
|
||||
return () => {
|
||||
setTopbarEditView(null);
|
||||
changePageSubtitle('');
|
||||
changePaymentReceiveView(null);
|
||||
};
|
||||
}, [customViewId, addPaymentReceivesTableQueries, changePaymentReceiveView]);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
onViewChanged && onViewChanged(customViewId);
|
||||
}, [customViewId]);
|
||||
|
||||
const debounceChangeHistory = useRef(
|
||||
debounce((toUrl) => {
|
||||
history.push(toUrl);
|
||||
}, 250),
|
||||
);
|
||||
|
||||
const handleTabsChange = (viewId) => {
|
||||
const toPath = viewId ? `${viewId}/custom_view` : '';
|
||||
debounceChangeHistory.current(`/payment-receives/${toPath}`);
|
||||
setTopbarEditView(viewId);
|
||||
};
|
||||
const tabs = paymentReceivesViews.map((view) => ({
|
||||
...pick(view, ['name', 'id']),
|
||||
}));
|
||||
|
||||
// Handle click a new view tab.
|
||||
const handleClickNewView = () => {
|
||||
setTopbarEditView(null);
|
||||
history.push('/custom_views/payment-receives/new');
|
||||
};
|
||||
|
||||
return (
|
||||
<Navbar className={'navbar--dashboard-views'}>
|
||||
<NavbarGroup align={Alignment.LEFT}>
|
||||
<DashboardViewsTabs
|
||||
initialViewId={customViewId}
|
||||
baseUrl={'/payment-receives'}
|
||||
tabs={tabs}
|
||||
onNewViewTabClick={handleClickNewView}
|
||||
onChange={handleTabsChange}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
viewId: ownProps.match.params.custom_view_id,
|
||||
});
|
||||
|
||||
const withPaymentReceivesViewTabs = connect(mapStateToProps);
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
withPaymentReceivesViewTabs,
|
||||
withPaymentReceivesActions,
|
||||
withDashboardActions,
|
||||
withViewDetails(),
|
||||
withPaymentReceives(({ paymentReceivesViews }) => ({
|
||||
paymentReceivesViews,
|
||||
})),
|
||||
)(PaymentReceiveViewTabs);
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
@@ -8,7 +8,9 @@ import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import withCustomersActions from 'containers/Customers/withCustomersActions';
|
||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||
import withItemsActions from 'containers/Items/withItemsActions';
|
||||
//#withInvoiceActions
|
||||
import withPaymentReceivesActions from './withPaymentReceivesActions';
|
||||
import withInvoiceActions from '../Invoice/withInvoiceActions';
|
||||
import withInvoices from '../Invoice/withInvoices';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
@@ -22,9 +24,16 @@ function PaymentReceives({
|
||||
//#withItemsActions
|
||||
requestFetchItems,
|
||||
|
||||
//#withInvoiceActions
|
||||
//#withPaymentReceivesActions
|
||||
requestFetchPaymentReceive,
|
||||
|
||||
//#withInvoicesActions
|
||||
requestFetchDueInvoices,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { id } = useParams();
|
||||
const [customerId, setCustomerId] = useState(null);
|
||||
const [payload, setPayload] = useState(false);
|
||||
|
||||
// Handle fetch accounts data
|
||||
const fetchAccounts = useQuery('accounts-list', (key) =>
|
||||
@@ -32,30 +41,59 @@ function PaymentReceives({
|
||||
);
|
||||
|
||||
// Handle fetch Items data table or list
|
||||
const fetchItems = useQuery('items-table', () => requestFetchItems({}));
|
||||
const fetchItems = useQuery('items-list', () => requestFetchItems({}));
|
||||
|
||||
// Handle fetch customers data table or list
|
||||
const fetchCustomers = useQuery('customers-table', () =>
|
||||
const fetchCustomers = useQuery('customers-list', () =>
|
||||
requestFetchCustomers({}),
|
||||
);
|
||||
|
||||
const handleFormSubmit = useCallback((payload) => {}, [history]);
|
||||
const fetchPaymentReceive = useQuery(
|
||||
['payment-receive', id],
|
||||
(key, _id) => requestFetchPaymentReceive(_id),
|
||||
{ enabled: !!id },
|
||||
);
|
||||
|
||||
const fetchDueInvoices = useQuery(
|
||||
['due-invoies', customerId],
|
||||
(key, query) => requestFetchDueInvoices(query),
|
||||
{ enabled: !!customerId },
|
||||
);
|
||||
|
||||
const handleFormSubmit = useCallback(
|
||||
(payload) => {
|
||||
payload.redirect && history.push('/payment-receives');
|
||||
},
|
||||
[history],
|
||||
);
|
||||
const handleCancel = useCallback(() => {
|
||||
history.goBack();
|
||||
}, [history]);
|
||||
|
||||
const handleCustomerChange = (customerId) => {
|
||||
if (id) {
|
||||
setCustomerId(!customerId);
|
||||
} else {
|
||||
setCustomerId(customerId);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardInsider
|
||||
loading={
|
||||
fetchCustomers.isFetching ||
|
||||
fetchItems.isFetching ||
|
||||
fetchAccounts.isFetching
|
||||
fetchAccounts.isFetching ||
|
||||
fetchPaymentReceive.isFetching
|
||||
}
|
||||
name={'payment-receive'}
|
||||
>
|
||||
<PaymentReceiveForm
|
||||
onFormSubmit={handleFormSubmit}
|
||||
paymentReceiveId={id}
|
||||
paymentReceiveInvoices={id}
|
||||
onCancelForm={handleCancel}
|
||||
onCustomerChange={handleCustomerChange}
|
||||
/>
|
||||
</DashboardInsider>
|
||||
);
|
||||
@@ -65,5 +103,6 @@ export default compose(
|
||||
withCustomersActions,
|
||||
withItemsActions,
|
||||
withAccountsActions,
|
||||
// withInvoiceActions
|
||||
withPaymentReceivesActions,
|
||||
withInvoiceActions,
|
||||
)(PaymentReceives);
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
import React, { useEffect, useCallback, useState, useMemo } from 'react';
|
||||
import {
|
||||
Intent,
|
||||
Button,
|
||||
Classes,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { withRouter } from 'react-router';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import moment from 'moment';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import { DataTable, Money, Icon } from 'components';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withViewDetails from 'containers/Views/withViewDetails';
|
||||
|
||||
import withPaymentReceives from './withPaymentReceives';
|
||||
import withPaymentReceivesActions from './withPaymentReceivesActions';
|
||||
import withCurrentView from 'containers/Views/withCurrentView';
|
||||
|
||||
function PaymentReceivesDataTable({
|
||||
//#withPaymentReceives
|
||||
PaymentReceivesCurrentPage,
|
||||
paymentReceivesPageination,
|
||||
paymentReceivesLoading,
|
||||
paymentReceivesItems,
|
||||
|
||||
// #withDashboardActions
|
||||
changeCurrentView,
|
||||
changePageSubtitle,
|
||||
setTopbarEditView,
|
||||
|
||||
// #withView
|
||||
viewMeta,
|
||||
|
||||
//#OwnProps
|
||||
loading,
|
||||
onFetchData,
|
||||
onEditPaymentReceive,
|
||||
onDeletePaymentReceive,
|
||||
onSelectedRowsChange,
|
||||
}) {
|
||||
const [initialMount, setInitialMount] = useState(false);
|
||||
const { custom_view_id: customViewId } = useParams();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
useEffect(() => {
|
||||
setInitialMount(false);
|
||||
}, [customViewId]);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
if (!paymentReceivesLoading) {
|
||||
setInitialMount(true);
|
||||
}
|
||||
}, [paymentReceivesLoading, setInitialMount]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (customViewId) {
|
||||
// changeCurrentView(customViewId);
|
||||
// setTopbarEditView(customViewId);
|
||||
// }
|
||||
// changePageSubtitle(customViewId && viewMeta ? viewMeta.name : '');
|
||||
// }, [
|
||||
// customViewId,
|
||||
// changeCurrentView,
|
||||
// changePageSubtitle,
|
||||
// setTopbarEditView,
|
||||
// viewMeta,
|
||||
// ]);
|
||||
|
||||
const handleEditPaymentReceive = useCallback(
|
||||
(paymentReceive) => () => {
|
||||
onEditPaymentReceive && onEditPaymentReceive(paymentReceive);
|
||||
},
|
||||
[onEditPaymentReceive],
|
||||
);
|
||||
|
||||
const handleDeletePaymentReceive = useCallback(
|
||||
(paymentReceive) => () => {
|
||||
onDeletePaymentReceive && onDeletePaymentReceive(paymentReceive);
|
||||
},
|
||||
[onDeletePaymentReceive],
|
||||
);
|
||||
|
||||
const actionMenuList = useCallback(
|
||||
(paymentReceive) => (
|
||||
<Menu>
|
||||
<MenuItem text={formatMessage({ id: 'view_details' })} />
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'edit_payment_receive' })}
|
||||
onClick={handleEditPaymentReceive(paymentReceive)}
|
||||
/>
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'delete_payment_receive' })}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeletePaymentReceive(paymentReceive)}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
/>
|
||||
</Menu>
|
||||
),
|
||||
[handleDeletePaymentReceive, handleEditPaymentReceive, formatMessage],
|
||||
);
|
||||
|
||||
const onRowContextMenu = useCallback(
|
||||
(cell) => {
|
||||
return actionMenuList(cell.row.original);
|
||||
},
|
||||
[actionMenuList],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'payment_date',
|
||||
Header: formatMessage({ id: 'payment_date' }),
|
||||
accessor: (r) => moment(r.payment_date).format('YYYY MMM DD'),
|
||||
width: 140,
|
||||
className: 'payment_date',
|
||||
},
|
||||
{
|
||||
id: 'customer_id',
|
||||
Header: formatMessage({ id: 'customer_name' }),
|
||||
accessor: 'customer.display_name',
|
||||
width: 140,
|
||||
className: 'customer_id',
|
||||
},
|
||||
{
|
||||
id: 'payment_receive_no',
|
||||
Header: formatMessage({ id: 'payment_receive_no' }),
|
||||
accessor: (row) => `#${row.payment_receive_no}`,
|
||||
width: 140,
|
||||
className: 'payment_receive_no',
|
||||
},
|
||||
{
|
||||
id: 'amount',
|
||||
Header: formatMessage({ id: 'amount' }),
|
||||
accessor: (r) => <Money amount={r.amount} currency={'USD'} />,
|
||||
width: 140,
|
||||
className: 'amount',
|
||||
},
|
||||
{
|
||||
id: 'reference_no',
|
||||
Header: formatMessage({ id: 'reference_no' }),
|
||||
accessor: 'reference_no',
|
||||
width: 140,
|
||||
className: 'reference_no',
|
||||
},
|
||||
{
|
||||
id: 'deposit_account_id',
|
||||
Header: formatMessage({ id: 'deposit_account' }),
|
||||
accessor: 'deposit_account.name',
|
||||
width: 140,
|
||||
className: 'deposit_account_id',
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: '',
|
||||
Cell: ({ cell }) => (
|
||||
<Popover
|
||||
content={actionMenuList(cell.row.original)}
|
||||
position={Position.RIGHT_BOTTOM}
|
||||
>
|
||||
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
||||
</Popover>
|
||||
),
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
disableResizing: true,
|
||||
},
|
||||
],
|
||||
[actionMenuList, formatMessage],
|
||||
);
|
||||
|
||||
const handleDataTableFetchData = useCallback(
|
||||
(...args) => {
|
||||
onFetchData && onFetchData(...args);
|
||||
},
|
||||
[onFetchData],
|
||||
);
|
||||
const handleSelectedRowsChange = useCallback(
|
||||
(selectedRows) => {
|
||||
onSelectedRowsChange &&
|
||||
onSelectedRowsChange(selectedRows.map((s) => s.original));
|
||||
},
|
||||
[onSelectedRowsChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LoadingIndicator loading={loading} mount={false}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={PaymentReceivesCurrentPage}
|
||||
onFetchData={handleDataTableFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
noInitialFetch={true}
|
||||
sticky={true}
|
||||
loading={paymentReceivesLoading && !initialMount}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
rowContextMenu={onRowContextMenu}
|
||||
pagination={true}
|
||||
pagesCount={paymentReceivesPageination.pagesCount}
|
||||
initialPageSize={paymentReceivesPageination.pageSize}
|
||||
initialPageIndex={paymentReceivesPageination.page - 1}
|
||||
/>
|
||||
</LoadingIndicator>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
withCurrentView,
|
||||
withDialogActions,
|
||||
withDashboardActions,
|
||||
withPaymentReceivesActions,
|
||||
withPaymentReceives(
|
||||
({
|
||||
PaymentReceivesCurrentPage,
|
||||
paymentReceivesLoading,
|
||||
paymentReceivesPageination,
|
||||
}) => ({
|
||||
PaymentReceivesCurrentPage,
|
||||
paymentReceivesLoading,
|
||||
paymentReceivesPageination,
|
||||
}),
|
||||
),
|
||||
withViewDetails(),
|
||||
)(PaymentReceivesDataTable);
|
||||
@@ -0,0 +1,15 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
getPaymentReceiveByIdFactory,
|
||||
getPaymentReceiveInvoices,
|
||||
} from 'store/PaymentReceive/paymentReceive.selector';
|
||||
|
||||
export default () => {
|
||||
const getPaymentReceiveById = getPaymentReceiveByIdFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
paymentReceive: getPaymentReceiveById(state, props),
|
||||
paymentReceiveInvoices: getPaymentReceiveInvoices(state, props),
|
||||
});
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { getResourceViews } from 'store/customViews/customViews.selectors';
|
||||
import {
|
||||
getPaymentReceiveCurrentPageFactory,
|
||||
getPaymentReceivePaginationMetaFactory,
|
||||
getPaymentReceiveTableQuery,
|
||||
} from 'store/PaymentReceive/paymentReceive.selector';
|
||||
|
||||
|
||||
export default (mapState) => {
|
||||
const getPyamentReceivesItems = getPaymentReceiveCurrentPageFactory();
|
||||
const getPyamentReceivesPaginationMeta = getPaymentReceivePaginationMetaFactory();
|
||||
const mapStateToProps = (state, props) => {
|
||||
const query = getPaymentReceiveTableQuery(state, props);
|
||||
const mapped = {
|
||||
PaymentReceivesCurrentPage: getPyamentReceivesItems(state, props, query),
|
||||
paymentReceivesViews: getResourceViews(state, props, 'payment_receives'),
|
||||
paymentReceivesItems: state.paymentReceives.items,
|
||||
paymentReceivesTableQuery: query,
|
||||
paymentReceivesPageination: getPyamentReceivesPaginationMeta(state, props, query),
|
||||
paymentReceivesLoading: state.paymentReceives.loading,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
@@ -13,7 +13,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(submitPaymentReceive({ form })),
|
||||
requestFetchPaymentReceive: (id) => dispatch(fetchPaymentReceive({ id })),
|
||||
requestEditPaymentReceive: (id, form) =>
|
||||
dispatch(editPaymentReceive({ id, form })),
|
||||
dispatch(editPaymentReceive( id, form )),
|
||||
requestDeletePaymentReceive: (id) => dispatch(deletePaymentReceive({ id })),
|
||||
requestFetchPaymentReceiveTable: (query = {}) =>
|
||||
dispatch(fetchPaymentReceivesTable({ query: { ...query } })),
|
||||
|
||||
@@ -12,6 +12,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 { Row, Col } from 'react-grid-system';
|
||||
|
||||
import ReceiptFromHeader from './ReceiptFormHeader';
|
||||
import EstimatesItemsTable from 'containers/Sales/Estimate/EntriesItemsTable';
|
||||
@@ -214,7 +215,7 @@ function ReceiptForm({
|
||||
const requestForm = { ...form };
|
||||
|
||||
if (receipt && receipt.id) {
|
||||
requestEditReceipt(receipt.id && requestForm).then(() => {
|
||||
requestEditReceipt(receipt.id, requestForm).then(() => {
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'the_receipt_has_been_successfully_edited',
|
||||
@@ -244,6 +245,7 @@ function ReceiptForm({
|
||||
}
|
||||
},
|
||||
});
|
||||
console.log(formik.errors, 'ERROR');
|
||||
|
||||
const handleDeleteFile = useCallback(
|
||||
(_deletedFiles) => {
|
||||
@@ -296,31 +298,38 @@ function ReceiptForm({
|
||||
onClickClearAllLines={handleClearAllLines}
|
||||
formik={formik}
|
||||
/>
|
||||
<FormGroup
|
||||
label={<T id={'receipt_message'} />}
|
||||
className={'form-group--'}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('receipt_message')}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={<T id={'statement'} />}
|
||||
className={'form-group--statement'}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('statement')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<Dragzone
|
||||
initialFiles={initialAttachmentFiles}
|
||||
onDrop={handleDropFiles}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
hint={'Attachments: Maxiumum size: 20MB'}
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
<FormGroup
|
||||
label={<T id={'receipt_message'} />}
|
||||
className={'form-group--'}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('receipt_message')}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={<T id={'statement'} />}
|
||||
className={'form-group--statement'}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
{...formik.getFieldProps('statement')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<Dragzone
|
||||
initialFiles={initialAttachmentFiles}
|
||||
onDrop={handleDropFiles}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
hint={'Attachments: Maxiumum size: 20MB'}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</form>
|
||||
<ReceiptFormFooter
|
||||
formik={formik}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function ReceiptFormFooter({
|
||||
receipt,
|
||||
}) {
|
||||
return (
|
||||
<div className={'estimate-form__floating-footer'}>
|
||||
<div className={'form__floating-footer'}>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
|
||||
@@ -83,13 +83,17 @@ function ReceiptFormHeader({
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{/* customer name */}
|
||||
<div class="page-form receipt-form">
|
||||
<div class="page-form__primary-section">
|
||||
{/*- Customer name -*/}
|
||||
<FormGroup
|
||||
label={<T id={'customer_name'} />}
|
||||
inline={true}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
className={classNames(
|
||||
'form-group--select-list',
|
||||
Classes.FILL,
|
||||
'form-group--customer',
|
||||
)}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
intent={errors.customer_id && touched.customer_id && Intent.DANGER}
|
||||
helperText={
|
||||
@@ -110,10 +114,11 @@ function ReceiptFormHeader({
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{/*- Deposit account -*/}
|
||||
<FormGroup
|
||||
label={<T id={'deposit_account'} />}
|
||||
className={classNames(
|
||||
'form-group--deposit_account_id',
|
||||
'form-group--deposit-account',
|
||||
'form-group--select-list',
|
||||
Classes.FILL,
|
||||
)}
|
||||
@@ -172,6 +177,7 @@ function ReceiptFormHeader({
|
||||
/>
|
||||
</FormGroup> */}
|
||||
|
||||
{/*- Reference -*/}
|
||||
<FormGroup
|
||||
label={<T id={'reference'} />}
|
||||
inline={true}
|
||||
@@ -185,6 +191,8 @@ function ReceiptFormHeader({
|
||||
{...getFieldProps('reference_no')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{/*- Send to email -*/}
|
||||
<FormGroup
|
||||
label={<T id={'send_to_email'} />}
|
||||
inline={true}
|
||||
|
||||
@@ -23,14 +23,14 @@ function Receipts({
|
||||
requestFetchItems,
|
||||
|
||||
//#withReceiptsActions
|
||||
requsetFetchInvoice,
|
||||
requestFetchReceipt,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { id } = useParams();
|
||||
|
||||
const fetchReceipt = useQuery(
|
||||
['receipt', id],
|
||||
(key, _id) => requsetFetchInvoice(_id),
|
||||
(key, _id) => requestFetchReceipt(_id),
|
||||
{ enabled: !!id },
|
||||
);
|
||||
const fetchAccounts = useQuery('accounts-list', (key) =>
|
||||
@@ -60,7 +60,7 @@ function Receipts({
|
||||
loading={
|
||||
fetchCustomers.isFetching ||
|
||||
fetchItems.isFetching ||
|
||||
fetchAccounts.isFetching||
|
||||
fetchAccounts.isFetching ||
|
||||
fetchReceipt.isFetching
|
||||
}
|
||||
name={'receipt-form'}
|
||||
|
||||
@@ -2,23 +2,25 @@ import { connect } from 'react-redux';
|
||||
import { getResourceViews } from 'store/customViews/customViews.selectors';
|
||||
import {
|
||||
getReceiptCurrentPageFactory,
|
||||
getReceiptsTableQuery,
|
||||
getReceiptsTableQueryFactory,
|
||||
getReceiptsPaginationMetaFactory,
|
||||
} from 'store/receipt/receipt.selector';
|
||||
|
||||
export default (mapState) => {
|
||||
const getReceiptsItems = getReceiptCurrentPageFactory();
|
||||
const getReceiptPaginationMeta = getReceiptsPaginationMetaFactory();
|
||||
const getReceiptsTableQuery = getReceiptsTableQueryFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const query = getReceiptsTableQuery(state, props);
|
||||
const tableQuery = getReceiptsTableQuery(state, props);
|
||||
|
||||
const mapped = {
|
||||
receiptsCurrentPage: getReceiptsItems(state, props, query),
|
||||
receiptsCurrentPage: getReceiptsItems(state, props, tableQuery),
|
||||
receiptview:getResourceViews(state, props, 'sales_receipts'),
|
||||
receiptItems: state.sales_receipts.items,
|
||||
receiptTableQuery: query,
|
||||
receiptsPagination: getReceiptPaginationMeta(state, props, query),
|
||||
receiptsLoading: state.sales_receipts.loading,
|
||||
receiptItems: state.salesReceipts.items,
|
||||
receiptTableQuery: tableQuery,
|
||||
receiptsPagination: getReceiptPaginationMeta(state, props, tableQuery),
|
||||
receiptsLoading: state.salesReceipts.loading,
|
||||
};
|
||||
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
|
||||
@@ -13,7 +13,7 @@ const mapDipatchToProps = (dispatch) => ({
|
||||
requestEditVendor: (id, form) => dispatch(editVendor(id, form)),
|
||||
requestFetchVendorsTable: (query = {}) =>
|
||||
dispatch(fetchVendorsTable({ query: { ...query } })),
|
||||
requestDeleteEstimate: (id) => dispatch(deleteVendor({ id })),
|
||||
requestDeleteVender: (id) => dispatch(deleteVendor({ id })),
|
||||
|
||||
changeVendorView: (id) =>
|
||||
dispatch({
|
||||
|
||||
@@ -393,6 +393,9 @@ export default {
|
||||
base_currency_: 'Base currency',
|
||||
date_format_: 'Date format',
|
||||
category_name_: 'Category name',
|
||||
sell_account_: 'Sell account',
|
||||
cost_account_: 'Cost account',
|
||||
inventory_account_: 'Inventory account',
|
||||
view_name_: 'View name',
|
||||
time_zone: 'Time zone',
|
||||
location: 'Location',
|
||||
@@ -563,7 +566,6 @@ export default {
|
||||
you_could_not_delete_account_has_child_accounts:
|
||||
'You could not delete account has child accounts.',
|
||||
journal_entry: 'Journal Entry',
|
||||
|
||||
estimate: 'Estimate #',
|
||||
estimate_date: 'Estimate Date',
|
||||
expiration_date: 'Expiration Date',
|
||||
@@ -586,7 +588,6 @@ export default {
|
||||
rate: 'Rate',
|
||||
estimate_list: 'Estimate List',
|
||||
estimate_number: 'Estimate Number',
|
||||
|
||||
product_and_service: 'Product/Service',
|
||||
the_estimate_has_been_successfully_edited:
|
||||
'The estimate #{number} has been successfully edited.',
|
||||
@@ -594,9 +595,7 @@ export default {
|
||||
'The estimate #{number} has been successfully created.',
|
||||
the_estimate_has_been_successfully_deleted:
|
||||
'The estimate has been successfully deleted.',
|
||||
|
||||
once_delete_this_estimate_you_will_able_to_restore_it: `Once you delete this estimate, you won\'t be able to restore it later. Are you sure you want to delete this estimate?`,
|
||||
|
||||
cannot_be_zero_or_empty: 'cannot be zero or empty.',
|
||||
invocies: 'Invoices',
|
||||
invoices_list: 'Invoices List',
|
||||
@@ -609,21 +608,21 @@ export default {
|
||||
due_date_: 'Due date',
|
||||
invoice_message: 'Invoice Message',
|
||||
reference_no: 'Reference No',
|
||||
|
||||
invocie_number: 'Invoice Number',
|
||||
invoice_amount: 'Invoice Amount',
|
||||
amount_due: 'Amount Due',
|
||||
payment_amount: 'Payment Amount',
|
||||
edit_invoice: 'Edit Invoice',
|
||||
delete_invoice: 'Delete Invoice',
|
||||
new_invoice: 'New Invoice',
|
||||
invoice_list: 'Invoice List',
|
||||
|
||||
the_invoice_has_been_successfully_edited:
|
||||
'The invoice #{number} has been successfully edited.',
|
||||
the_invocie_has_been_successfully_created:
|
||||
'The invoice #{number} has been successfully created.',
|
||||
the_invocie_has_been_successfully_deleted:
|
||||
'The invoice has been successfully deleted.',
|
||||
|
||||
once_delete_this_invoice_you_will_able_to_restore_it: `Once you delete this invoice, you won\'t be able to restore it later. Are you sure you want to delete this invoice?`,
|
||||
|
||||
receipt_list: 'Receipt List',
|
||||
receipts: 'Receipts',
|
||||
receipt: 'Receipt #',
|
||||
@@ -640,16 +639,13 @@ export default {
|
||||
deposit_account: 'Deposit Account',
|
||||
send_to_email: 'Send to email',
|
||||
select_deposit_account: 'Select Deposit Account',
|
||||
|
||||
once_delete_this_receipt_you_will_able_to_restore_it: `Once you delete this receipt, you won\'t be able to restore it later. Are you sure you want to delete this receipt?`,
|
||||
|
||||
the_receipt_has_been_successfully_created:
|
||||
'The recepit has been successfully created.',
|
||||
the_receipt_has_been_successfully_edited:
|
||||
'The receipt has been successfully edited.',
|
||||
the_receipt_has_been_successfully_deleted:
|
||||
'The receipt has been successfully deleted.',
|
||||
|
||||
bill_list: 'Bill List',
|
||||
bills: 'Bills',
|
||||
accept: 'Accept',
|
||||
@@ -665,27 +661,57 @@ export default {
|
||||
bill_number_: 'Bill number',
|
||||
vendor_name_: 'Vendor name',
|
||||
delete_bill: 'Delete Bill',
|
||||
|
||||
the_bill_has_been_successfully_edited:
|
||||
'The bill #{number} has been successfully edited.',
|
||||
|
||||
the_bill_has_been_successfully_created:
|
||||
'The bill has been successfully created.',
|
||||
|
||||
the_bill_has_been_successfully_deleted:
|
||||
'The bill has been successfully deleted.',
|
||||
|
||||
once_delete_this_bill_you_will_able_to_restore_it: `Once you delete this bill, you won\'t be able to restore it later. Are you sure you want to delete this bill?`,
|
||||
|
||||
deposit_to: 'Deposit to',
|
||||
edit_payment_receive: 'Edit Payment Receive',
|
||||
delete_payment_receive: 'Delete Payment Receive',
|
||||
payment_Receive_list: 'Payment Receive List',
|
||||
payment_receive: 'Payment Receive',
|
||||
new_payment_receive: 'New Payment Receive',
|
||||
payment_receives: 'Payment Receives',
|
||||
payment_receive_no: 'Payment Receive #',
|
||||
payment_receive_no_: 'Payment receive no',
|
||||
receive_amount: 'Receive Amount',
|
||||
receive_amount_: 'Receive amount',
|
||||
the_payment_receive_has_been_successfully_created:
|
||||
'The payment receive has been successfully created.',
|
||||
|
||||
the_payment_receive_has_been_successfully_deleted:
|
||||
'The payment receive has been successfully deleted.',
|
||||
the_payment_receive_has_been_successfully_edited:
|
||||
'The payment receive #{number} has been successfully edited.',
|
||||
once_delete_this_payment_receive_you_will_able_to_restore_it: `Once you delete this payment receive, you won\'t be able to restore it later. Are you sure you want to delete this payment receive?`,
|
||||
select_invoice: 'Select Invoice',
|
||||
|
||||
payment_mades: 'Payment Mades',
|
||||
payment_made: 'Payment Made',
|
||||
edit_payment_made: 'Edit Payment Made',
|
||||
delete_payment_made: 'Delete Payment Made',
|
||||
vendor_name: 'Vendor Name',
|
||||
payment_number: 'Payment Number',
|
||||
payment_no: 'Payment Number #',
|
||||
vendor_name_: 'Vendor name',
|
||||
bill_amount: 'Bill Amount',
|
||||
payment_account_: 'Payment account',
|
||||
payment_no_: 'Payment number',
|
||||
new_payment_made: 'New Payment Made',
|
||||
payment_made_list: 'Payment Made List',
|
||||
payment_account: 'Payment Account',
|
||||
select_vender_account: 'Select Vender Account',
|
||||
select_payment_account: 'Select Payment Account',
|
||||
the_payment_made_has_been_successfully_edited:
|
||||
'The payment made has been successfully edited.',
|
||||
the_payment_made_has_been_successfully_created:
|
||||
'The payment made has been successfully created.',
|
||||
the_payment_made_has_been_successfully_deleted:
|
||||
'The payment made has been successfully deleted.',
|
||||
once_delete_this_payment_made_you_will_able_to_restore_it: `Once you delete this payment made, you won\'t be able to restore it later. Are you sure you want to delete this payment made?`,
|
||||
sellable: 'Sellable',
|
||||
purchasable: 'Purchasable',
|
||||
sell_account: 'Sell Account',
|
||||
cost_account: 'Cost Account',
|
||||
inventory_account: 'Inventory Account',
|
||||
};
|
||||
|
||||
@@ -292,6 +292,14 @@ export default [
|
||||
}),
|
||||
breadcrumb: 'New Payment Receive',
|
||||
},
|
||||
{
|
||||
path: `/payment-receives`,
|
||||
component: LazyLoader({
|
||||
loader: () =>
|
||||
import('containers/Sales/PaymentReceive/PaymentReceiveList'),
|
||||
}),
|
||||
breadcrumb: 'Payment Receive List',
|
||||
},
|
||||
|
||||
//Bills
|
||||
{
|
||||
@@ -322,4 +330,36 @@ export default [
|
||||
}),
|
||||
breadcrumb: 'Receipt List',
|
||||
},
|
||||
|
||||
// Payment Mades
|
||||
|
||||
{
|
||||
path: `/payment-made/:id/edit`,
|
||||
component: LazyLoader({
|
||||
loader: () => import('containers/Purchases/PaymentMades/PaymentMade'),
|
||||
}),
|
||||
breadcrumb: 'Edit',
|
||||
},
|
||||
{
|
||||
path: `/payment-made/new`,
|
||||
component: LazyLoader({
|
||||
loader: () => import('containers/Purchases/PaymentMades/PaymentMade'),
|
||||
}),
|
||||
breadcrumb: 'New Payment Made',
|
||||
},
|
||||
{
|
||||
path: `/payment-mades`,
|
||||
component: LazyLoader({
|
||||
loader: () =>
|
||||
import('containers/Purchases/PaymentMades/PaymentMadeList'),
|
||||
}),
|
||||
breadcrumb: 'Payment Made List',
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
];
|
||||
|
||||
@@ -66,27 +66,7 @@ export const deleteBill = ({ id }) => {
|
||||
};
|
||||
|
||||
export const submitBill = ({ form }) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resolve, reject) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
||||
});
|
||||
ApiService.post('purchases/bills', form)
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
reject(data?.errors);
|
||||
});
|
||||
});
|
||||
return (dispatch) => ApiService.post('purchases/bills', form);
|
||||
};
|
||||
|
||||
export const fetchBill = ({ id }) => {
|
||||
@@ -94,12 +74,11 @@ export const fetchBill = ({ id }) => {
|
||||
new Promise((resovle, reject) => {
|
||||
ApiService.get(`purchases/bills/${id}`)
|
||||
.then((response) => {
|
||||
const { bill } = response.data;
|
||||
|
||||
dispatch({
|
||||
type: t.BILL_SET,
|
||||
payload: {
|
||||
id,
|
||||
bill: response.data.bill,
|
||||
},
|
||||
payload: { id, bill },
|
||||
});
|
||||
resovle(response);
|
||||
})
|
||||
@@ -112,25 +91,5 @@ export const fetchBill = ({ id }) => {
|
||||
};
|
||||
|
||||
export const editBill = (id, form) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resolve, rejcet) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
||||
});
|
||||
ApiService.post(`purchases/bills/${id}`, form)
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
rejcet(data?.errors);
|
||||
});
|
||||
});
|
||||
return (dispatch) => ApiService.post(`purchases/bills/${id}`, form);
|
||||
};
|
||||
|
||||
@@ -37,9 +37,13 @@ const reducer = createReducer(initialState, {
|
||||
[t.BILLS_ITEMS_SET]: (state, action) => {
|
||||
const { bills } = action.payload;
|
||||
const _bills = {};
|
||||
|
||||
bills.forEach((bill) => {
|
||||
const oldBill = state.items[bill.id] || {};
|
||||
|
||||
_bills[bill.id] = {
|
||||
...defaultBill,
|
||||
...oldBill,
|
||||
...bill,
|
||||
};
|
||||
});
|
||||
@@ -59,7 +63,6 @@ const reducer = createReducer(initialState, {
|
||||
|
||||
[t.BILLS_PAGE_SET]: (state, action) => {
|
||||
const { customViewId, bills, pagination } = action.payload;
|
||||
|
||||
const viewId = customViewId || -1;
|
||||
const view = state.views[viewId] || {};
|
||||
|
||||
@@ -95,8 +98,6 @@ const reducer = createReducer(initialState, {
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default createTableQueryReducers('bills', reducer);
|
||||
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors';
|
||||
|
||||
const billTableQuery = (state) => {
|
||||
return state.bills.tableQuery;
|
||||
};
|
||||
|
||||
export const getBillTableQuery = createSelector(
|
||||
paginationLocationQuery,
|
||||
billTableQuery,
|
||||
(locationQuery, tableQuery) => {
|
||||
return {
|
||||
...locationQuery,
|
||||
...tableQuery,
|
||||
};
|
||||
},
|
||||
);
|
||||
const billTableQuery = (state) => state.bills.tableQuery;
|
||||
|
||||
const billPageSelector = (state, props, query) => {
|
||||
const viewId = state.bills.currentViewId;
|
||||
return state.bills.views?.[viewId]?.pages?.[query.page];
|
||||
};
|
||||
const billItemsSelector = (state) => state.bills.items;
|
||||
|
||||
const billItemsSelector = (state) => {
|
||||
return state.bills.items;
|
||||
const billByIdSelector = (state, props) => state.bills.items[props.billId];
|
||||
|
||||
const billPaginationSelector = (state, props) => {
|
||||
const viewId = state.bills.currentViewId;
|
||||
return state.bills.views?.[viewId];
|
||||
};
|
||||
|
||||
export const getBillTableQueryFactory = () =>
|
||||
createSelector(
|
||||
paginationLocationQuery,
|
||||
billTableQuery,
|
||||
(locationQuery, tableQuery) => {
|
||||
return {
|
||||
...locationQuery,
|
||||
...tableQuery,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Get current page bills items.
|
||||
* @return {Array}
|
||||
*/
|
||||
export const getBillCurrentPageFactory = () =>
|
||||
createSelector(billPageSelector, billItemsSelector, (billPage, billItems) => {
|
||||
return typeof billPage === 'object'
|
||||
@@ -32,20 +39,14 @@ export const getBillCurrentPageFactory = () =>
|
||||
: [];
|
||||
});
|
||||
|
||||
const billByIdSelector = (state, props) => {
|
||||
return state.bills.items[props.billId];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve bill details of the given bill id.
|
||||
*/
|
||||
export const getBillByIdFactory = () =>
|
||||
createSelector(billByIdSelector, (bill) => {
|
||||
return bill;
|
||||
});
|
||||
|
||||
const billPaginationSelector = (state, props) => {
|
||||
const viewId = state.bills.currentViewId;
|
||||
return state.bills.views?.[viewId];
|
||||
};
|
||||
|
||||
export const getBillPaginationMetaFactory = () =>
|
||||
createSelector(billPaginationSelector, (billPage) => {
|
||||
return billPage?.paginationMeta || {};
|
||||
|
||||
@@ -8,7 +8,6 @@ export const submitEstimate = ({ form }) => {
|
||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
||||
});
|
||||
ApiService.post('sales/estimates', form)
|
||||
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
@@ -21,7 +20,6 @@ export const submitEstimate = ({ form }) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
|
||||
reject(data?.errors);
|
||||
});
|
||||
});
|
||||
@@ -30,23 +28,14 @@ export const submitEstimate = ({ form }) => {
|
||||
export const editEstimate = (id, form) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resolve, reject) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
||||
});
|
||||
ApiService.post(`sales/estimates/${id}`, form)
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
reject(data?.errors);
|
||||
});
|
||||
});
|
||||
@@ -74,12 +63,12 @@ export const fetchEstimate = ({ id }) => {
|
||||
new Promise((resovle, reject) => {
|
||||
ApiService.get(`sales/estimates/${id}`)
|
||||
.then((response) => {
|
||||
|
||||
const { estimate } = response.data;
|
||||
dispatch({
|
||||
type: t.ESTIMATE_SET,
|
||||
payload: {
|
||||
id,
|
||||
estimate: response.data.estimate,
|
||||
estimate,
|
||||
},
|
||||
});
|
||||
resovle(response);
|
||||
@@ -95,7 +84,7 @@ export const fetchEstimate = ({ id }) => {
|
||||
export const fetchEstimatesTable = ({ query = {} }) => {
|
||||
return (dispatch, getState) =>
|
||||
new Promise((resolve, rejcet) => {
|
||||
const pageQuery = getState().sales_estimates.tableQuery;
|
||||
const pageQuery = getState().salesEstimates.tableQuery;
|
||||
dispatch({
|
||||
type: t.ESTIMATES_TABLE_LOADING,
|
||||
payload: {
|
||||
@@ -106,7 +95,6 @@ export const fetchEstimatesTable = ({ query = {} }) => {
|
||||
params: { ...pageQuery, ...query },
|
||||
})
|
||||
.then((response) => {
|
||||
// debugger;
|
||||
dispatch({
|
||||
type: t.ESTIMATES_PAGE_SET,
|
||||
payload: {
|
||||
|
||||
@@ -59,6 +59,7 @@ const reducer = createReducer(initialState, {
|
||||
},
|
||||
|
||||
[t.ESTIMATES_PAGE_SET]: (state, action) => {
|
||||
// @todo camelCase keys.
|
||||
const { customViewId, sales_estimates, pagination } = action.payload;
|
||||
|
||||
const viewId = customViewId || -1;
|
||||
|
||||
@@ -1,27 +1,33 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors';
|
||||
|
||||
const estimateTableQuery = (state) => {
|
||||
return state.sales_estimates.tableQuery;
|
||||
};
|
||||
const estimateTableQuery = (state) => state.salesEstimates.tableQuery;
|
||||
|
||||
export const getEstimatesTableQuery = createSelector(
|
||||
paginationLocationQuery,
|
||||
estimateTableQuery,
|
||||
(locationQuery, tableQuery) => {
|
||||
return {
|
||||
...locationQuery,
|
||||
...tableQuery,
|
||||
};
|
||||
},
|
||||
);
|
||||
const estimateByIdSelector = (state, props) =>
|
||||
state.salesEstimates.items[props.estimateId];
|
||||
|
||||
const estimatesCurrentViewSelector = (state, props) => {
|
||||
const viewId = state.salesEstimates.currentViewId;
|
||||
return state.salesEstimates.views?.[viewId];
|
||||
};
|
||||
const estimateItemsSelector = (state) => state.salesEstimates.items;
|
||||
|
||||
const estimatesPageSelector = (state, props, query) => {
|
||||
const viewId = state.sales_estimates.currentViewId;
|
||||
return state.sales_estimates.views?.[viewId]?.pages?.[query.page];
|
||||
const viewId = state.salesEstimates.currentViewId;
|
||||
return state.salesEstimates.views?.[viewId]?.pages?.[query.page];
|
||||
};
|
||||
|
||||
const estimateItemsSelector = (state) => state.sales_estimates.items;
|
||||
export const getEstimatesTableQueryFactory = () =>
|
||||
createSelector(
|
||||
paginationLocationQuery,
|
||||
estimateTableQuery,
|
||||
(locationQuery, tableQuery) => {
|
||||
return {
|
||||
...locationQuery,
|
||||
...tableQuery,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export const getEstimateCurrentPageFactory = () =>
|
||||
createSelector(
|
||||
@@ -34,21 +40,12 @@ export const getEstimateCurrentPageFactory = () =>
|
||||
},
|
||||
);
|
||||
|
||||
const estimateByIdSelector = (state, props) => {
|
||||
return state.sales_estimates.items[props.estimateId];
|
||||
};
|
||||
|
||||
export const getEstimateByIdFactory = () =>
|
||||
createSelector(estimateByIdSelector, (estimate) => {
|
||||
return estimate;
|
||||
});
|
||||
|
||||
const estimatesPaginationSelector = (state, props) => {
|
||||
const viewId = state.sales_estimates.currentViewId;
|
||||
return state.sales_estimates.views?.[viewId];
|
||||
};
|
||||
|
||||
export const getEstimatesPaginationMetaFactory = () =>
|
||||
createSelector(estimatesPaginationSelector, (estimatePage) => {
|
||||
return estimatePage?.paginationMeta || {};
|
||||
createSelector(estimatesCurrentViewSelector, (estimateView) => {
|
||||
return estimateView?.paginationMeta || {};
|
||||
});
|
||||
|
||||
@@ -4,22 +4,13 @@ import t from 'store/types';
|
||||
export const submitInvoice = ({ form }) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resolve, reject) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
||||
});
|
||||
ApiService.post('sales/invoices', form)
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
|
||||
reject(data?.errors);
|
||||
});
|
||||
@@ -51,18 +42,12 @@ export const editInvoice = (id, form) => {
|
||||
});
|
||||
ApiService.post(`sales/invoices/${id}`, form)
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
reject(data?.errors);
|
||||
});
|
||||
});
|
||||
@@ -71,7 +56,7 @@ export const editInvoice = (id, form) => {
|
||||
export const fetchInvoicesTable = ({ query } = {}) => {
|
||||
return (dispatch, getState) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const pageQuery = getState().sales_invoices.tableQuery;
|
||||
const pageQuery = getState().salesInvoices.tableQuery;
|
||||
dispatch({
|
||||
type: t.INVOICES_TABLE_LOADING,
|
||||
payload: {
|
||||
@@ -90,6 +75,7 @@ export const fetchInvoicesTable = ({ query } = {}) => {
|
||||
customViewId: response.data.customViewId || -1,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: t.INVOICES_ITEMS_SET,
|
||||
payload: {
|
||||
@@ -138,3 +124,26 @@ export const fetchInvoice = ({ id }) => {
|
||||
});
|
||||
});
|
||||
};
|
||||
export const dueInvoices = ({ id }) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resovle, reject) => {
|
||||
ApiService.get(`sales/invoices/due_invoices`, {
|
||||
params: { customer_id: id },
|
||||
})
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.DUE_INVOICES_SET,
|
||||
payload: {
|
||||
customer_id: id,
|
||||
due_sales_invoices: response.data.due_sales_invoices,
|
||||
},
|
||||
});
|
||||
resovle(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
reject(data?.errors);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ const initialState = {
|
||||
page_size: 5,
|
||||
page: 1,
|
||||
},
|
||||
dueInvoices: {},
|
||||
};
|
||||
|
||||
const defaultInvoice = {
|
||||
@@ -22,13 +23,13 @@ const reducer = createReducer(initialState, {
|
||||
[t.INVOICE_SET]: (state, action) => {
|
||||
const { id, sale_invoice } = action.payload;
|
||||
const _invoice = state.items[id] || {};
|
||||
|
||||
state.items[id] = { ...defaultInvoice, ..._invoice, ...sale_invoice };
|
||||
},
|
||||
|
||||
[t.INVOICES_ITEMS_SET]: (state, action) => {
|
||||
const { sales_invoices } = action.payload;
|
||||
const _invoices = {};
|
||||
|
||||
sales_invoices.forEach((invoice) => {
|
||||
_invoices[invoice.id] = {
|
||||
...defaultInvoice,
|
||||
@@ -96,6 +97,40 @@ const reducer = createReducer(initialState, {
|
||||
},
|
||||
};
|
||||
},
|
||||
[t.DUE_INVOICES_SET]: (state, action) => {
|
||||
const { customer_id, due_sales_invoices } = action.payload;
|
||||
|
||||
const _dueInvoices = [];
|
||||
|
||||
state.dueInvoices[customer_id] = due_sales_invoices.map((due) => due.id);
|
||||
const _invoices = {};
|
||||
due_sales_invoices.forEach((invoice) => {
|
||||
_invoices[invoice.id] = {
|
||||
...invoice,
|
||||
};
|
||||
});
|
||||
|
||||
state.items = {
|
||||
...state.dueInvoices,
|
||||
...state.items.dueInvoices,
|
||||
..._invoices,
|
||||
};
|
||||
},
|
||||
[t.RELOAD_INVOICES]: (state, action) => {
|
||||
const { sales_invoices } = action.payload;
|
||||
|
||||
const _sales_invoices = {};
|
||||
sales_invoices.forEach((invoice) => {
|
||||
_sales_invoices[invoice.id] = {
|
||||
...invoice,
|
||||
};
|
||||
});
|
||||
|
||||
state.items = {
|
||||
...state.items,
|
||||
..._sales_invoices,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default createTableQueryReducers('sales_invoices', reducer);
|
||||
|
||||
@@ -1,27 +1,38 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors';
|
||||
|
||||
const invoiceTableQuery = (state) => state.sales_invoices.tableQuery;
|
||||
|
||||
export const getInvoiceTableQuery = createSelector(
|
||||
import {
|
||||
pickItemsFromIds,
|
||||
paginationLocationQuery,
|
||||
invoiceTableQuery,
|
||||
(locationQuery, tableQuery) => {
|
||||
return {
|
||||
...locationQuery,
|
||||
...tableQuery,
|
||||
};
|
||||
},
|
||||
);
|
||||
getItemById,
|
||||
} from 'store/selectors';
|
||||
|
||||
const invoiceTableQuery = (state) => state.salesInvoices.tableQuery;
|
||||
|
||||
const invoicesByIdSelector = (state, props) =>
|
||||
state.salesInvoices.items[props.invoiceId];
|
||||
|
||||
const invoicesPaginationSelector = (state, props) => {
|
||||
const viewId = state.salesInvoices.currentViewId;
|
||||
return state.salesInvoices.views?.[viewId];
|
||||
};
|
||||
|
||||
const invoicesPageSelector = (state, props, query) => {
|
||||
const viewId = state.sales_invoices.currentViewId;
|
||||
return state.sales_invoices.views?.[viewId]?.pages?.[query.page];
|
||||
const viewId = state.salesInvoices.currentViewId;
|
||||
return state.salesInvoices.views?.[viewId]?.pages?.[query.page];
|
||||
};
|
||||
|
||||
const invoicesItemsSelector = (state) => {
|
||||
return state.sales_invoices.items;
|
||||
};
|
||||
const invoicesItemsSelector = (state) => state.salesInvoices.items;
|
||||
|
||||
export const getInvoiceTableQueryFactory = () =>
|
||||
createSelector(
|
||||
paginationLocationQuery,
|
||||
invoiceTableQuery,
|
||||
(locationQuery, tableQuery) => {
|
||||
return {
|
||||
...locationQuery,
|
||||
...tableQuery,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export const getInvoiceCurrentPageFactory = () =>
|
||||
createSelector(
|
||||
@@ -34,21 +45,27 @@ export const getInvoiceCurrentPageFactory = () =>
|
||||
},
|
||||
);
|
||||
|
||||
const invoicesByIdSelector = (state, props) => {
|
||||
return state.sales_invoices.items[props.invoiceId];
|
||||
};
|
||||
|
||||
export const getInvoiecsByIdFactory = () =>
|
||||
createSelector(invoicesByIdSelector, (invoice) => {
|
||||
return invoice;
|
||||
});
|
||||
|
||||
const invoicesPaginationSelector = (state, props) => {
|
||||
const viewId = state.sales_invoices.currentViewId;
|
||||
return state.sales_invoices.views?.[viewId];
|
||||
};
|
||||
|
||||
export const getInvoicePaginationMetaFactory = () =>
|
||||
createSelector(invoicesPaginationSelector, (invoicePage) => {
|
||||
return invoicePage?.paginationMeta || {};
|
||||
});
|
||||
|
||||
const dueInvoicesSelector = (state, props) => {
|
||||
return state.salesInvoices.dueInvoices[props.customer_id] || [];
|
||||
};
|
||||
|
||||
export const getdueInvoices = createSelector(
|
||||
dueInvoicesSelector,
|
||||
invoicesItemsSelector,
|
||||
(customerIds, items) => {
|
||||
|
||||
return typeof customerIds === 'object'
|
||||
? pickItemsFromIds(items, customerIds) || []
|
||||
: [];
|
||||
},
|
||||
);
|
||||
|
||||
@@ -9,4 +9,6 @@ export default {
|
||||
INVOICES_PAGINATION_SET: 'INVOICES_PAGINATION_SET',
|
||||
INVOICES_PAGE_SET: 'INVOICES_PAGE_SET',
|
||||
INVOICES_ITEMS_SET: 'INVOICES_ITEMS_SET',
|
||||
DUE_INVOICES_SET: 'DUE_INVOICES_SET',
|
||||
RELOAD_INVOICES: 'RELOAD_INVOICES',
|
||||
};
|
||||
|
||||
128
client/src/store/PaymentMades/paymentMade.actions.js
Normal file
128
client/src/store/PaymentMades/paymentMade.actions.js
Normal file
@@ -0,0 +1,128 @@
|
||||
import ApiService from 'services/ApiService';
|
||||
import t from 'store/types';
|
||||
|
||||
export const submitPaymentMade = ({ form }) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resolve, reject) => {
|
||||
ApiService.post('purchases/bill_payments', form)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
|
||||
reject(data?.errors);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const deletePaymentMade = ({ id }) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resovle, reject) => {
|
||||
ApiService.delete(`purchases/bill_payments/${id}`)
|
||||
.then((response) => {
|
||||
dispatch({ type: t.PAYMENT_MADE_DELETE, payload: { id } });
|
||||
resovle(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error.response.data.errors || []);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const editPaymentMade = (id, form) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resolve, rejcet) => {
|
||||
ApiService.post(`purchases/bill_payments/${id}`, form)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
|
||||
rejcet(data?.errors);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchPaymentMadesTable = ({ query = {} }) => {
|
||||
return (dispatch, getState) =>
|
||||
new Promise((resolve, rejcet) => {
|
||||
const pageQuery = getState().paymentMades.tableQuery;
|
||||
|
||||
dispatch({
|
||||
type: t.PAYMENT_MADES_TABLE_LOADING,
|
||||
payload: {
|
||||
loading: true,
|
||||
},
|
||||
});
|
||||
ApiService.get('purchases/bill_payments', {
|
||||
params: { ...pageQuery, ...query },
|
||||
})
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.PAYMENT_MADES_PAGE_SET,
|
||||
payload: {
|
||||
bill_payments: response.data.bill_payments.results,
|
||||
pagination: response.data.bill_payments.pagination,
|
||||
customViewId: response.data.customViewId || -1,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: t.PAYMENT_MADES_ITEMS_SET,
|
||||
payload: {
|
||||
bill_payments: response.data.bill_payments.results,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: t.PAYMENT_MADES_PAGINATION_SET,
|
||||
payload: {
|
||||
pagination: response.data.bill_payments.pagination,
|
||||
customViewId: response.data.customViewId || -1,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: t.PAYMENT_MADES_TABLE_LOADING,
|
||||
payload: {
|
||||
loading: false,
|
||||
},
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
rejcet(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchPaymentMade = ({ id }) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resovle, reject) => {
|
||||
ApiService.get(`purchases/bill_payments/${id}`, {})
|
||||
.then((response) => {
|
||||
// dispatch({
|
||||
// type: t.RELOAD_INVOICES,
|
||||
// payload: {
|
||||
// sales_invoices: response.data.paymentReceive.entries.map(
|
||||
// (e) => e.invoice,
|
||||
// ),
|
||||
// },
|
||||
// });
|
||||
dispatch({
|
||||
type: t.PAYMENT_MADE_SET,
|
||||
payload: {
|
||||
id,
|
||||
bill_payment: response.data.bill_payment,
|
||||
},
|
||||
});
|
||||
resovle(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
reject(data?.errors);
|
||||
});
|
||||
});
|
||||
};
|
||||
88
client/src/store/PaymentMades/paymentMade.reducer.js
Normal file
88
client/src/store/PaymentMades/paymentMade.reducer.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { createReducer } from '@reduxjs/toolkit';
|
||||
import { createTableQueryReducers } from 'store/queryReducers';
|
||||
import { omit } from 'lodash';
|
||||
import t from 'store/types';
|
||||
|
||||
const initialState = {
|
||||
items: {},
|
||||
views: {},
|
||||
loading: false,
|
||||
currentViewId: -1,
|
||||
tableQuery: {
|
||||
page_size: 5,
|
||||
page: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const defaultPaymentMade = {
|
||||
entries: [],
|
||||
};
|
||||
|
||||
const reducer = createReducer(initialState, {
|
||||
[t.PAYMENT_MADES_TABLE_LOADING]: (state, action) => {
|
||||
const { loading } = action.payload;
|
||||
state.loading = loading;
|
||||
},
|
||||
[t.PAYMENT_MADES_ITEMS_SET]: (state, action) => {
|
||||
const { bill_payments } = action.payload;
|
||||
const _bill_payments = {};
|
||||
bill_payments.forEach((billPayment) => {
|
||||
_bill_payments[billPayment.id] = {
|
||||
...defaultPaymentMade,
|
||||
...billPayment,
|
||||
};
|
||||
});
|
||||
state.items = {
|
||||
...state.items,
|
||||
..._bill_payments,
|
||||
};
|
||||
},
|
||||
|
||||
[t.PAYMENT_MADE_DELETE]: (state, action) => {
|
||||
const { id } = action.payload;
|
||||
|
||||
if (typeof state.items[id] !== 'undefined') {
|
||||
delete state.items[id];
|
||||
}
|
||||
},
|
||||
|
||||
[t.PAYMENT_MADES_PAGINATION_SET]: (state, action) => {
|
||||
const { pagination, customViewId } = action.payload;
|
||||
|
||||
const mapped = {
|
||||
pageSize: parseInt(pagination.pageSize, 10),
|
||||
page: parseInt(pagination.page, 10),
|
||||
total: parseInt(pagination.total, 10),
|
||||
};
|
||||
const paginationMeta = {
|
||||
...mapped,
|
||||
pagesCount: Math.ceil(mapped.total / mapped.pageSize),
|
||||
pageIndex: Math.max(mapped.page - 1, 0),
|
||||
};
|
||||
state.views = {
|
||||
...state.views,
|
||||
[customViewId]: {
|
||||
...(state.views?.[customViewId] || {}),
|
||||
paginationMeta,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
[t.PAYMENT_MADES_PAGE_SET]: (state, action) => {
|
||||
const { customViewId, bill_payments, pagination } = action.payload;
|
||||
|
||||
const viewId = customViewId || -1;
|
||||
const view = state.views[viewId] || {};
|
||||
|
||||
state.views[viewId] = {
|
||||
...view,
|
||||
pages: {
|
||||
...(state.views?.[viewId]?.pages || {}),
|
||||
[pagination.page]: {
|
||||
ids: bill_payments.map((i) => i.id),
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
export default createTableQueryReducers('bill_payments', reducer);
|
||||
56
client/src/store/PaymentMades/paymentMade.selector.js
Normal file
56
client/src/store/PaymentMades/paymentMade.selector.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors';
|
||||
|
||||
|
||||
|
||||
const paymentMadeTableQuery = (state) => state.paymentMades.tableQuery;
|
||||
|
||||
const paymentMadesPageSelector = (state, props, query) => {
|
||||
const viewId = state.paymentMades.currentViewId;
|
||||
return state.paymentMades.views?.[viewId]?.pages?.[query.page];
|
||||
};
|
||||
|
||||
const paymentMadesItemsSelector = (state) => {
|
||||
return state.paymentMades.items;
|
||||
};
|
||||
|
||||
const PaymentMadePaginationSelector = (state, props) => {
|
||||
const viewId = state.paymentMades.currentViewId;
|
||||
return state.paymentMades.views?.[viewId];
|
||||
};
|
||||
|
||||
const paymentMadesIds = (state, props) => {
|
||||
return state.paymentMades.items[props.paymentMadeId];
|
||||
};
|
||||
|
||||
export const getPaymentMadeCurrentPageFactory = () =>
|
||||
createSelector(
|
||||
paymentMadesPageSelector,
|
||||
paymentMadesItemsSelector,
|
||||
(Page, Items) => {
|
||||
return typeof Page === 'object'
|
||||
? pickItemsFromIds(Items, Page.ids) || []
|
||||
: [];
|
||||
},
|
||||
);
|
||||
|
||||
export const getPaymentMadeTableQuery = createSelector(
|
||||
paginationLocationQuery,
|
||||
paymentMadeTableQuery,
|
||||
(locationQuery, tableQuery) => {
|
||||
return {
|
||||
...locationQuery,
|
||||
...tableQuery,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export const getPaymentMadePaginationMetaFactory = () =>
|
||||
createSelector(PaymentMadePaginationSelector, (Page) => {
|
||||
return Page?.paginationMeta || {};
|
||||
});
|
||||
|
||||
export const getPaymentMadeByIdFactory = () =>
|
||||
createSelector(paymentMadesIds, (payment_Made) => {
|
||||
return payment_Made;
|
||||
});
|
||||
11
client/src/store/PaymentMades/paymentMade.type.js
Normal file
11
client/src/store/PaymentMades/paymentMade.type.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export default {
|
||||
PAYMENT_MADE_LIST_SET: 'PAYMENT_MADE_LIST_SET',
|
||||
PAYMENT_MADE_DELETE: 'PAYMENT_MADE_DELETE',
|
||||
PAYMENT_MADE_SET: 'PAYMENT_MADE_SET',
|
||||
PAYMENT_MADE_SET_CURRENT_VIEW: 'PAYMENT_MADE_SET_CURRENT_VIEW',
|
||||
PAYMENT_MADE_TABLE_QUERIES_ADD: 'PAYMENT_MADE_TABLE_QUERIES_ADD',
|
||||
PAYMENT_MADES_TABLE_LOADING: 'PAYMENT_MADES_TABLE_LOADING',
|
||||
PAYMENT_MADES_PAGE_SET: 'PAYMENT_MADES_PAGE_SET',
|
||||
PAYMENT_MADES_ITEMS_SET: 'PAYMENT_MADES_ITEMS_SET',
|
||||
PAYMENT_MADES_PAGINATION_SET: 'PAYMENT_MADES_PAGINATION_SET',
|
||||
};
|
||||
@@ -4,22 +4,14 @@ import t from 'store/types';
|
||||
export const submitPaymentReceive = ({ form }) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resolve, reject) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
||||
});
|
||||
ApiService.post('sales/payment_receives', form)
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
|
||||
reject(data?.errors);
|
||||
});
|
||||
});
|
||||
@@ -28,22 +20,14 @@ export const submitPaymentReceive = ({ form }) => {
|
||||
export const editPaymentReceive = (id, form) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resolve, rejcet) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
||||
});
|
||||
ApiService.post(`sales/payment_receives/${id}`, form)
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
|
||||
rejcet(data?.errors);
|
||||
});
|
||||
});
|
||||
@@ -52,9 +36,9 @@ export const editPaymentReceive = (id, form) => {
|
||||
export const deletePaymentReceive = ({ id }) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resovle, reject) => {
|
||||
ApiService.delete(`payment_receives/${id}`)
|
||||
ApiService.delete(`sales/payment_receives/${id}`)
|
||||
.then((response) => {
|
||||
dispatch({ type: t.PAYMENT_RECEIVE_DELETE });
|
||||
dispatch({ type: t.PAYMENT_RECEIVE_DELETE, payload: { id } });
|
||||
resovle(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -66,21 +50,31 @@ export const deletePaymentReceive = ({ id }) => {
|
||||
export const fetchPaymentReceive = ({ id }) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resovle, reject) => {
|
||||
ApiService.get(`payment_receives/${id}`)
|
||||
ApiService.get(`sales/payment_receives/${id}`, {})
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.RELOAD_INVOICES,
|
||||
payload: {
|
||||
sales_invoices: response.data.paymentReceive.entries.map(
|
||||
(e) => e.invoice,
|
||||
),
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: t.PAYMENT_RECEIVE_SET,
|
||||
payload: {
|
||||
id,
|
||||
payment_receive: response.data.payment_receive,
|
||||
paymentReceive: response.data.paymentReceive,
|
||||
|
||||
},
|
||||
});
|
||||
resovle(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
reject(data?.errors);
|
||||
// const { response } = error;
|
||||
// const { data } = response;
|
||||
// reject(data?.errors);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -88,7 +82,7 @@ export const fetchPaymentReceive = ({ id }) => {
|
||||
export const fetchPaymentReceivesTable = ({ query = {} }) => {
|
||||
return (dispatch, getState) =>
|
||||
new Promise((resolve, rejcet) => {
|
||||
const pageQuery = getState().payment_receive.tableQuery;
|
||||
const pageQuery = getState().paymentReceives.tableQuery;
|
||||
|
||||
dispatch({
|
||||
type: t.PAYMENT_RECEIVES_TABLE_LOADING,
|
||||
@@ -96,12 +90,12 @@ export const fetchPaymentReceivesTable = ({ query = {} }) => {
|
||||
loading: true,
|
||||
},
|
||||
});
|
||||
ApiService.get('payment_receives', {
|
||||
ApiService.get('sales/payment_receives', {
|
||||
params: { ...pageQuery, ...query },
|
||||
})
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.RECEIPTS_PAGE_SET,
|
||||
type: t.PAYMENT_RECEIVES_PAGE_SET,
|
||||
payload: {
|
||||
payment_receives: response.data.payment_receives.results,
|
||||
pagination: response.data.payment_receives.pagination,
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { createReducer } from '@reduxjs/toolkit';
|
||||
import { createTableQueryReducers } from 'store/queryReducers';
|
||||
import { omit } from 'lodash';
|
||||
import t from 'store/types';
|
||||
|
||||
const initialState = {
|
||||
items: {},
|
||||
views: {},
|
||||
loading: false,
|
||||
currentViewId: -1,
|
||||
tableQuery: {
|
||||
page_size: 5,
|
||||
page: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const defaultPaymentReceive = {
|
||||
entries: [],
|
||||
};
|
||||
|
||||
const reducer = createReducer(initialState, {
|
||||
[t.PAYMENT_RECEIVE_SET]: (state, action) => {
|
||||
const { id, paymentReceive } = action.payload;
|
||||
|
||||
const _paymentReceive = {
|
||||
...paymentReceive,
|
||||
entries: paymentReceive.entries.map((e) => {
|
||||
return { ...omit(e, ['invoice']) };
|
||||
}),
|
||||
};
|
||||
|
||||
const oldPaymentReceive = state.items[id] || {};
|
||||
state.items[id] = {
|
||||
...defaultPaymentReceive,
|
||||
...oldPaymentReceive,
|
||||
..._paymentReceive,
|
||||
};
|
||||
},
|
||||
|
||||
[t.PAYMENT_RECEIVES_TABLE_LOADING]: (state, action) => {
|
||||
const { loading } = action.payload;
|
||||
state.loading = loading;
|
||||
},
|
||||
|
||||
[t.PAYMENT_RECEIVES_ITEMS_SET]: (state, action) => {
|
||||
const { payment_receives } = action.payload;
|
||||
const _payment_receives = {};
|
||||
payment_receives.forEach((payment_receive) => {
|
||||
_payment_receives[payment_receive.id] = {
|
||||
...defaultPaymentReceive,
|
||||
...payment_receive,
|
||||
};
|
||||
});
|
||||
state.items = {
|
||||
...state.items,
|
||||
..._payment_receives,
|
||||
};
|
||||
},
|
||||
|
||||
[t.PAYMENT_RECEIVE_SET_CURRENT_VIEW]: (state, action) => {
|
||||
state.currentViewId = action.currentViewId;
|
||||
},
|
||||
|
||||
[t.PAYMENT_RECEIVE_DELETE]: (state, action) => {
|
||||
const { id } = action.payload;
|
||||
|
||||
if (typeof state.items[id] !== 'undefined') {
|
||||
delete state.items[id];
|
||||
}
|
||||
},
|
||||
|
||||
[t.PAYMENT_RECEIVES_PAGINATION_SET]: (state, action) => {
|
||||
const { pagination, customViewId } = action.payload;
|
||||
|
||||
const mapped = {
|
||||
pageSize: parseInt(pagination.pageSize, 10),
|
||||
page: parseInt(pagination.page, 10),
|
||||
total: parseInt(pagination.total, 10),
|
||||
};
|
||||
const paginationMeta = {
|
||||
...mapped,
|
||||
pagesCount: Math.ceil(mapped.total / mapped.pageSize),
|
||||
pageIndex: Math.max(mapped.page - 1, 0),
|
||||
};
|
||||
state.views = {
|
||||
...state.views,
|
||||
[customViewId]: {
|
||||
...(state.views?.[customViewId] || {}),
|
||||
paginationMeta,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
[t.PAYMENT_RECEIVES_PAGE_SET]: (state, action) => {
|
||||
const { customViewId, payment_receives, pagination } = action.payload;
|
||||
|
||||
const viewId = customViewId || -1;
|
||||
const view = state.views[viewId] || {};
|
||||
|
||||
state.views[viewId] = {
|
||||
...view,
|
||||
pages: {
|
||||
...(state.views?.[viewId]?.pages || {}),
|
||||
[pagination.page]: {
|
||||
ids: payment_receives.map((i) => i.id),
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
export default createTableQueryReducers('payment_receives', reducer);
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors';
|
||||
|
||||
const paymentReceivesPageSelector = (state, props, query) => {
|
||||
const viewId = state.paymentReceives.currentViewId;
|
||||
return state.paymentReceives.views?.[viewId]?.pages?.[query.page];
|
||||
};
|
||||
|
||||
const paymentReceivesItemsSelector = (state) => {
|
||||
return state.paymentReceives.items;
|
||||
};
|
||||
|
||||
export const getPaymentReceiveCurrentPageFactory = () =>
|
||||
createSelector(
|
||||
paymentReceivesPageSelector,
|
||||
paymentReceivesItemsSelector,
|
||||
(Page, Items) => {
|
||||
return typeof Page === 'object'
|
||||
? pickItemsFromIds(Items, Page.ids) || []
|
||||
: [];
|
||||
},
|
||||
);
|
||||
|
||||
const paymentReceiveTableQuery = (state) => state.paymentReceives.tableQuery;
|
||||
|
||||
export const getPaymentReceiveTableQuery = createSelector(
|
||||
paginationLocationQuery,
|
||||
paymentReceiveTableQuery,
|
||||
(locationQuery, tableQuery) => {
|
||||
return {
|
||||
...locationQuery,
|
||||
...tableQuery,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const PaymentReceivePaginationSelector = (state, props) => {
|
||||
const viewId = state.paymentReceives.currentViewId;
|
||||
return state.paymentReceives.views?.[viewId];
|
||||
};
|
||||
|
||||
export const getPaymentReceivePaginationMetaFactory = () =>
|
||||
createSelector(PaymentReceivePaginationSelector, (Page) => {
|
||||
return Page?.paginationMeta || {};
|
||||
});
|
||||
|
||||
const invoicesItems = (state) => {
|
||||
return state.salesInvoices.items;
|
||||
};
|
||||
|
||||
|
||||
const payemntReceiveById = (state, props) => {
|
||||
return state.paymentReceives.items[props.paymentReceiveId];
|
||||
};
|
||||
|
||||
export const getPaymentReceiveByIdFactory = () =>
|
||||
createSelector(payemntReceiveById, (payment_receive) => {
|
||||
return payment_receive;
|
||||
});
|
||||
|
||||
|
||||
const paymentReceiveInvoicesIdss = (state, props) => {
|
||||
return state.paymentReceives.items[props.paymentReceiveInvoices]
|
||||
};
|
||||
|
||||
// const invoicesItems = (state) => {
|
||||
// return state.sales_invoices.items;
|
||||
// };
|
||||
|
||||
// export const = createSelector(
|
||||
// paymentReceiveInvoicesIds,
|
||||
// invoicesItems,
|
||||
// (ids, items) => {},
|
||||
// );
|
||||
|
||||
export const getPaymentReceiveInvoices = createSelector(
|
||||
payemntReceiveById,
|
||||
invoicesItems,
|
||||
(paymentRecieve, items) => {
|
||||
return typeof paymentRecieve === 'object'
|
||||
? pickItemsFromIds(
|
||||
items,
|
||||
paymentRecieve.entries.map((entry) => entry.invoice_id),
|
||||
)
|
||||
: [];
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
PAYMENT_RECEIVE_LIST_SET: 'PAYMENT_RECEIVE_LIST_SET',
|
||||
PAYMENT_RECEIVE_SET: 'PAYMENT_RECEIVE_SET',
|
||||
PAYMENT_RECEIVE_DELETE: 'PAYMENT_RECEIVE_DELETE',
|
||||
PAYMENT_RECEIVE_SET: 'PAYMENT_RECEIVE_SET',
|
||||
PAYMENT_RECEIVE_SET_CURRENT_VIEW: 'PAYMENT_RECEIVE_SET_CURRENT_VIEW',
|
||||
PAYMENT_RECEIVE_TABLE_QUERIES_ADD: 'PAYMENT_RECEIVE_TABLE_QUERIES_ADD',
|
||||
PAYMENT_RECEIVES_TABLE_LOADING: 'PAYMENT_RECEIVES_TABLE_LOADING',
|
||||
|
||||
@@ -3,6 +3,7 @@ export default {
|
||||
ITEMS_CATEGORY_DATA_TABLE: 'ITEMS_CATEGORY_DATA_TABLE',
|
||||
CATEGORY_DELETE: 'CATEGORY_DELETE',
|
||||
ITEM_CATEGORIES_TABLE_LOADING: 'ITEM_CATEGORIES_TABLE_LOADING',
|
||||
ITEM_CATEGORIES_BULK_DELETE:'ITEM_CATEGORIES_BULK_DELETE'
|
||||
|
||||
ITEM_CATEGORIES_BULK_DELETE: 'ITEM_CATEGORIES_BULK_DELETE',
|
||||
ITEM_CATEGORIES_TABLE_QUERIES_ADD: 'ITEM_CATEGORIES_TABLE_QUERIES_ADD',
|
||||
ITEM_CATEGORIES_SET_CURRENT_VIEW: 'ITEM_CATEGORIES_SET_CURRENT_VIEW',
|
||||
};
|
||||
|
||||
@@ -45,22 +45,14 @@ export const deleteReceipt = ({ id }) => {
|
||||
export const editReceipt = (id, form) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resolve, rejcet) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
||||
});
|
||||
ApiService.post(`sales/receipts/${id}`, form)
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
|
||||
rejcet(data?.errors);
|
||||
});
|
||||
});
|
||||
@@ -91,7 +83,7 @@ export const fetchReceipt = ({ id }) => {
|
||||
export const fetchReceiptsTable = ({ query = {} }) => {
|
||||
return (dispatch, getState) =>
|
||||
new Promise((resolve, rejcet) => {
|
||||
const pageQuery = getState().sales_receipts.tableQuery;
|
||||
const pageQuery = getState().salesReceipts.tableQuery;
|
||||
dispatch({
|
||||
type: t.RECEIPTS_TABLE_LOADING,
|
||||
payload: {
|
||||
|
||||
@@ -2,14 +2,22 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors';
|
||||
|
||||
const receiptsPageSelector = (state, props, query) => {
|
||||
const viewId = state.sales_receipts.currentViewId;
|
||||
return state.sales_receipts.views?.[viewId]?.pages?.[query.page];
|
||||
const viewId = state.salesReceipts.currentViewId;
|
||||
return state.salesReceipts.views?.[viewId]?.pages?.[query.page];
|
||||
};
|
||||
|
||||
const receiptItemsSelector = (state) => {
|
||||
return state.sales_receipts.items;
|
||||
const receiptsPaginationSelector = (state, props) => {
|
||||
const viewId = state.salesReceipts.currentViewId;
|
||||
return state.salesReceipts.views?.[viewId];
|
||||
};
|
||||
|
||||
const receiptItemsSelector = (state) => state.salesReceipts.items;
|
||||
|
||||
const receiptTableQuery = (state) => state.salesReceipts.tableQuery;
|
||||
|
||||
const receiptByIdSelector = (state, props) => state.salesReceipts.items[props.receiptId];
|
||||
|
||||
|
||||
export const getReceiptCurrentPageFactory = () =>
|
||||
createSelector(
|
||||
receiptsPageSelector,
|
||||
@@ -21,35 +29,23 @@ export const getReceiptCurrentPageFactory = () =>
|
||||
},
|
||||
);
|
||||
|
||||
const receiptTableQuery = (state) => {
|
||||
return state.sales_receipts.tableQuery;
|
||||
};
|
||||
|
||||
export const getReceiptsTableQuery = createSelector(
|
||||
paginationLocationQuery,
|
||||
receiptTableQuery,
|
||||
(locationQuery, tableQuery) => {
|
||||
return {
|
||||
...locationQuery,
|
||||
...tableQuery,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const receiptByIdSelector = (state, props) => {
|
||||
return state.sales_receipts.items[props.receiptId];
|
||||
};
|
||||
export const getReceiptsTableQueryFactory = () =>
|
||||
createSelector(
|
||||
paginationLocationQuery,
|
||||
receiptTableQuery,
|
||||
(locationQuery, tableQuery) => {
|
||||
return {
|
||||
...locationQuery,
|
||||
...tableQuery,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export const getReceiptByIdFactory = () =>
|
||||
createSelector(receiptByIdSelector, (receipt) => {
|
||||
return receipt;
|
||||
});
|
||||
|
||||
const receiptsPaginationSelector = (state, props) => {
|
||||
const viewId = state.sales_receipts.currentViewId;
|
||||
return state.sales_receipts.views?.[viewId];
|
||||
};
|
||||
|
||||
export const getReceiptsPaginationMetaFactory = () =>
|
||||
createSelector(receiptsPaginationSelector, (receiptPage) => {
|
||||
return receiptPage?.paginationMeta || {};
|
||||
|
||||
@@ -18,11 +18,13 @@ import globalSearch from './search/search.reducer';
|
||||
import exchangeRates from './ExchangeRate/exchange.reducer';
|
||||
import globalErrors from './globalErrors/globalErrors.reducer';
|
||||
import customers from './customers/customers.reducer';
|
||||
import sales_estimates from './Estimate/estimates.reducer';
|
||||
import sales_invoices from './Invoice/invoices.reducer';
|
||||
import sales_receipts from './receipt/receipt.reducer';
|
||||
import salesEstimates from './Estimate/estimates.reducer';
|
||||
import salesInvoices from './Invoice/invoices.reducer';
|
||||
import salesReceipts from './receipt/receipt.reducer';
|
||||
import bills from './Bills/bills.reducer';
|
||||
import vendors from './vendors/vendors.reducer';
|
||||
import paymentReceives from './PaymentReceive/paymentReceive.reducer';
|
||||
import paymentMades from './PaymentMades/paymentMade.reducer';
|
||||
|
||||
export default combineReducers({
|
||||
authentication,
|
||||
@@ -43,9 +45,12 @@ export default combineReducers({
|
||||
exchangeRates,
|
||||
globalErrors,
|
||||
customers,
|
||||
sales_estimates,
|
||||
sales_invoices,
|
||||
sales_receipts,
|
||||
|
||||
salesEstimates,
|
||||
salesInvoices,
|
||||
salesReceipts,
|
||||
bills,
|
||||
vendors,
|
||||
paymentReceives,
|
||||
paymentMades
|
||||
});
|
||||
|
||||
@@ -21,8 +21,9 @@ import estimates from './Estimate/estimates.types';
|
||||
import invoices from './Invoice/invoices.types';
|
||||
import receipts from './receipt/receipt.type';
|
||||
import bills from './Bills/bills.type';
|
||||
import paymentReceives from './PaymentReceive/paymentReceive.type';
|
||||
import vendors from './vendors/vendors.types';
|
||||
import paymentReceives from './PaymentReceive/paymentReceive.type';
|
||||
import paymentMades from './PaymentMades/paymentMade.type';
|
||||
|
||||
export default {
|
||||
...authentication,
|
||||
@@ -50,4 +51,5 @@ export default {
|
||||
...bills,
|
||||
...paymentReceives,
|
||||
...vendors,
|
||||
...paymentMades,
|
||||
};
|
||||
|
||||
22
client/src/store/vendors/vendors.actions.js
vendored
22
client/src/store/vendors/vendors.actions.js
vendored
@@ -9,9 +9,7 @@ export const fetchVendorsTable = ({ query }) => {
|
||||
type: t.VENDORS_TABLE_LOADING,
|
||||
payload: { loading: true },
|
||||
});
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
||||
});
|
||||
|
||||
ApiService.get(`vendors`, { params: { ...pageQuery, ...query } })
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
@@ -22,14 +20,12 @@ export const fetchVendorsTable = ({ query }) => {
|
||||
customViewId: response.data.customViewId,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: t.VENDORS_ITEMS_SET,
|
||||
payload: {
|
||||
vendors: response.data.vendors.results,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: t.VENDORS_PAGINATION_SET,
|
||||
payload: {
|
||||
@@ -37,14 +33,11 @@ export const fetchVendorsTable = ({ query }) => {
|
||||
customViewId: response.data.customViewId || -1,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: t.VENDORS_TABLE_LOADING,
|
||||
payload: { loading: false },
|
||||
});
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -56,23 +49,16 @@ export const fetchVendorsTable = ({ query }) => {
|
||||
export const editVendor = ({ form, id }) => {
|
||||
return (dispatch) =>
|
||||
new Promise((resolve, reject) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
||||
});
|
||||
|
||||
ApiService.post(`vendors/${id}`, form)
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
dispatch({
|
||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||
});
|
||||
|
||||
reject(data?.errors);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user