This commit is contained in:
elforjani3
2020-09-30 21:26:21 +02:00
96 changed files with 4975 additions and 1695 deletions

View File

@@ -9,6 +9,7 @@ export default function AccountsSelectList({
selectedAccountId, selectedAccountId,
defaultSelectText = 'Select account', defaultSelectText = 'Select account',
onAccountSelected, onAccountSelected,
disabled = false,
}) { }) {
// Find initial account object to set it as default account in initial render. // Find initial account object to set it as default account in initial render.
const initialAccount = useMemo( const initialAccount = useMemo(
@@ -77,6 +78,7 @@ export default function AccountsSelectList({
onItemSelect={onAccountSelect} onItemSelect={onAccountSelect}
> >
<Button <Button
disabled={disabled}
text={selectedAccount ? selectedAccount.name : defaultSelectText} text={selectedAccount ? selectedAccount.name : defaultSelectText}
/> />
</Select> </Select>

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
export default function DivFieldCell({ cell: { value: initialValue } }) { export const DivFieldCell = ({ cell: { value: initialValue } }) => {
const [value, setValue] = useState(initialValue); const [value, setValue] = useState(initialValue);
useEffect(() => { useEffect(() => {
@@ -8,4 +8,13 @@ export default function DivFieldCell({ cell: { value: initialValue } }) {
}, [initialValue]); }, [initialValue]);
return <div>${value}</div>; return <div>${value}</div>;
} };
export const EmptyDiv = ({ cell: { value: initialValue } }) => {
const [value, setValue] = useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return <div>{value}</div>;
};

View File

@@ -4,7 +4,7 @@ import InputGroupCell from './InputGroupCell';
import ContactsListFieldCell from './ContactsListFieldCell'; import ContactsListFieldCell from './ContactsListFieldCell';
import EstimatesListFieldCell from './EstimatesListFieldCell'; import EstimatesListFieldCell from './EstimatesListFieldCell';
import PercentFieldCell from './PercentFieldCell'; import PercentFieldCell from './PercentFieldCell';
import DivFieldCell from './DivFieldCell'; import { DivFieldCell,EmptyDiv } from './DivFieldCell';
export { export {
AccountsListFieldCell, AccountsListFieldCell,
MoneyFieldCell, MoneyFieldCell,
@@ -13,4 +13,5 @@ export {
EstimatesListFieldCell, EstimatesListFieldCell,
PercentFieldCell, PercentFieldCell,
DivFieldCell, DivFieldCell,
EmptyDiv,
}; };

View File

@@ -1,5 +1,5 @@
import React, {useState, useMemo, useEffect, useCallback} from 'react'; import React, { useState, useMemo, useEffect, useCallback } from 'react';
import {InputGroup} from '@blueprintjs/core'; import { InputGroup } from '@blueprintjs/core';
const joinIntegerAndDecimal = (integer, decimal, separator) => { const joinIntegerAndDecimal = (integer, decimal, separator) => {
let output = `${integer}`; let output = `${integer}`;
@@ -16,13 +16,13 @@ const hasSeparator = (input, separator) => {
}; };
const addThousandSeparator = (integer, 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 toString = (number) => `${number}`;
const onlyNumbers = (input) => { const onlyNumbers = (input) => {
return toString(input).replace(/\D+/g, '') || '0' return toString(input).replace(/\D+/g, '') || '0';
}; };
const formatter = (value, options) => { const formatter = (value, options) => {
@@ -31,11 +31,19 @@ const formatter = (value, options) => {
const parts = toString(input).split(options.decimal); const parts = toString(input).split(options.decimal);
const integer = parseInt(onlyNumbers(parts[0]), 10); const integer = parseInt(onlyNumbers(parts[0]), 10);
const decimal = parts[1] ? onlyNumbers(parts[1]) : null; 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) 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) => { const unformatter = (input, options) => {
@@ -44,12 +52,12 @@ const unformatter = (input, options) => {
const integer = parseInt(onlyNumbers(parts[0]), 10); const integer = parseInt(onlyNumbers(parts[0]), 10);
const decimal = parts[1] ? onlyNumbers(parts[1]) : null; const decimal = parts[1] ? onlyNumbers(parts[1]) : null;
const separator = hasSeparator(input, options.decimal) const separator = hasSeparator(input, options.decimal)
? options.decimal : false; ? options.decimal
: false;
return `${navigate}${joinIntegerAndDecimal(integer, decimal, separator)}`; return `${navigate}${joinIntegerAndDecimal(integer, decimal, separator)}`;
}; };
export default function MoneyFieldGroup({ export default function MoneyFieldGroup({
value, value,
prefix = '', prefix = '',
@@ -59,32 +67,43 @@ export default function MoneyFieldGroup({
precision = 2, precision = 2,
inputGroupProps, inputGroupProps,
onChange, onChange,
disabled = false,
}) { }) {
const [state, setState] = useState(value); const [state, setState] = useState(value);
const options = useMemo(() => ({ const options = useMemo(
prefix, suffix, thousands, decimal, precision, () => ({
}), [ prefix,
prefix, suffix, thousands, decimal, precision, suffix,
]); thousands,
decimal,
precision,
}),
[prefix, suffix, thousands, decimal, precision],
);
const handleChange = useCallback((event) => { const handleChange = useCallback(
const formatted = formatter(event.target.value, options); (event) => {
const value = unformatter(event.target.value, options); const formatted = formatter(event.target.value, options);
const value = unformatter(event.target.value, options);
setState(formatted); setState(formatted);
onChange && onChange(event, value); onChange && onChange(event, value);
}, [onChange, options]); },
[onChange, options],
);
useEffect(() => { useEffect(() => {
const formatted = formatter(value, options); const formatted = formatter(value, options);
setState(formatted) setState(formatted);
}, [value, options, setState]); }, [value, options, setState]);
return ( return (
<InputGroup <InputGroup
value={state} value={state}
onChange={handleChange} onChange={handleChange}
{...inputGroupProps} /> {...inputGroupProps}
disabled={disabled}
/>
); );
} }

View File

@@ -1,4 +0,0 @@
export default {
DATATABLE_EDITOR: 'datatable-editor',
};

View File

@@ -40,37 +40,25 @@ export default [
children: [ children: [
{ {
text: <T id={'estimates'} />, text: <T id={'estimates'} />,
href: '/estimates/new', href: '/estimates',
}, },
// {
// text: <T id={'estimate_list'} />,
// href: '/estimates',
// },
{ {
text: <T id={'invocies'} />, text: <T id={'invocies'} />,
href: '/invoices/new', href: '/invoices',
}, },
// {
// text: <T id={'invoices_list'} />,
// href: '/invoices',
// },
{ {
text: <T id={'payment_receives'} />, text: <T id={'payment_receives'} />,
href: '/payment-receive/new', href: '/payment-receives',
}, },
{ {
divider: true, divider: true,
text: <T id={'invoices_list'} />,
href: '/invoices',
}, },
{ {
text: <T id={'receipts'} />, text: <T id={'receipts'} />,
href: '/receipts/new', href: '/receipts',
}, },
// {
// text: <T id={'receipt_list'} />,
// href: '/receipts',
// },
], ],
}, },
{ {
@@ -78,14 +66,12 @@ export default [
children: [ children: [
{ {
text: <T id={'bills'} />, 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',
}, },
], ],
}, },

View File

@@ -19,7 +19,6 @@ import withDialogRedux from 'components/DialogReduxConnect';
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import classNames from 'classnames'; import classNames from 'classnames';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import { getDialogPayload } from 'store/dashboard/dashboard.reducer';
import withCurrency from 'containers/Currencies/withCurrency'; import withCurrency from 'containers/Currencies/withCurrency';
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions'; import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
@@ -27,7 +26,7 @@ import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
import { compose } from 'utils'; import { compose } from 'utils';
function CurrencyDialog({ function CurrencyDialog({
name, dialogName,
payload, payload,
isOpen, isOpen,
@@ -66,6 +65,7 @@ function CurrencyDialog({
); );
const { const {
values,
errors, errors,
touched, touched,
isSubmitting, isSubmitting,
@@ -83,7 +83,7 @@ function CurrencyDialog({
if (payload.action === 'edit') { if (payload.action === 'edit') {
requestEditCurrency(currency.id, values) requestEditCurrency(currency.id, values)
.then((response) => { .then((response) => {
closeDialog(name); closeDialog(dialogName);
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage({
id: 'the_currency_has_been_successfully_edited', id: 'the_currency_has_been_successfully_edited',
@@ -99,7 +99,7 @@ function CurrencyDialog({
} else { } else {
requestSubmitCurrencies(values) requestSubmitCurrencies(values)
.then((response) => { .then((response) => {
closeDialog(name); closeDialog(dialogName);
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage({
id: 'the_currency_has_been_successfully_created', id: 'the_currency_has_been_successfully_created',
@@ -117,8 +117,8 @@ function CurrencyDialog({
}); });
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
closeDialog(name); closeDialog(dialogName);
}, [name, closeDialog]); }, [dialogName, closeDialog]);
const onDialogOpening = useCallback(() => { const onDialogOpening = useCallback(() => {
fetchCurrencies.refetch(); fetchCurrencies.refetch();
@@ -126,14 +126,14 @@ function CurrencyDialog({
const onDialogClosed = useCallback(() => { const onDialogClosed = useCallback(() => {
resetForm(); resetForm();
closeDialog(name); closeDialog(dialogName);
}, [closeDialog, name, resetForm]); }, [closeDialog, dialogName, resetForm]);
const requiredSpan = useMemo(() => <span className={'required'}>*</span>, []); const requiredSpan = useMemo(() => <span className={'required'}>*</span>, []);
return ( return (
<Dialog <Dialog
name={name} name={dialogName}
title={ title={
payload.action === 'edit' ? ( payload.action === 'edit' ? (
<T id={'edit_currency'} /> <T id={'edit_currency'} />
@@ -222,7 +222,7 @@ function CurrencyDialog({
} }
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => ({
dialogName: 'currency-form', currency: 'currency-form',
}); });
const withCurrencyFormDialog = connect(mapStateToProps); const withCurrencyFormDialog = connect(mapStateToProps);

View File

@@ -3,27 +3,31 @@ import { compose } from 'utils';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import withDialogRedux from 'components/DialogReduxConnect'; import withDialogRedux from 'components/DialogReduxConnect';
import withExchangeRateDetail from 'containers/ExchangeRates/withExchangeRateDetail';
import withExchangeRatesActions from 'containers/ExchangeRates/withExchangeRatesActions'; import withExchangeRatesActions from 'containers/ExchangeRates/withExchangeRatesActions';
import withExchangeRates from 'containers/ExchangeRates/withExchangeRates'; import withExchangeRates from 'containers/ExchangeRates/withExchangeRates';
import withCurrencies from 'containers/Currencies/withCurrencies'; import withCurrencies from 'containers/Currencies/withCurrencies';
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => ({
dialogName: 'exchangeRate-form', dialogName: 'exchangeRate-form',
exchangeRateId:
props.payload.action === 'edit' && props.payload.id
? props.payload.id
: null,
}); });
const withExchangeRateDialog = connect(mapStateToProps); const withExchangeRateDialog = connect(mapStateToProps);
export default compose( export default compose(
withExchangeRateDialog,
withDialogRedux(null, 'exchangeRate-form'), withDialogRedux(null, 'exchangeRate-form'),
withExchangeRateDialog,
withCurrencies(({ currenciesList }) => ({ withCurrencies(({ currenciesList }) => ({
currenciesList, currenciesList,
})), })),
withExchangeRatesActions,
withExchangeRateDetail,
withExchangeRates(({ exchangeRatesList }) => ({ withExchangeRates(({ exchangeRatesList }) => ({
exchangeRatesList, exchangeRatesList,
})), })),
withExchangeRatesActions,
withDialogActions, withDialogActions,
); );

View File

@@ -15,13 +15,8 @@ import { useFormik } from 'formik';
import { useQuery, queryCache } from 'react-query'; import { useQuery, queryCache } from 'react-query';
import moment from 'moment'; import moment from 'moment';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { momentFormatter } from 'utils'; import { momentFormatter, tansformDateValue } from 'utils';
import { import { AppToaster, Dialog, ErrorMessage, ListSelect } from 'components';
AppToaster,
Dialog,
ErrorMessage,
ListSelect,
} from 'components';
import classNames from 'classnames'; import classNames from 'classnames';
import withExchangeRatesDialog from './ExchangeRateDialog.container'; import withExchangeRatesDialog from './ExchangeRateDialog.container';
@@ -39,12 +34,13 @@ function ExchangeRateDialog({
// #withCurrencies // #withCurrencies
currenciesList, currenciesList,
//#WithExchangeRateDetail
exchangeRate,
// #withExchangeRatesActions // #withExchangeRatesActions
requestSubmitExchangeRate, requestSubmitExchangeRate,
requestFetchExchangeRates, requestFetchExchangeRates,
requestEditExchangeRate, requestEditExchangeRate,
requestFetchCurrencies,
editExchangeRate,
}) { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const [selectedItems, setSelectedItems] = useState({}); const [selectedItems, setSelectedItems] = useState({});
@@ -90,7 +86,7 @@ function ExchangeRateDialog({
validationSchema, validationSchema,
initialValues: { initialValues: {
...(payload.action === 'edit' && ...(payload.action === 'edit' &&
pick(editExchangeRate, Object.keys(initialValues))), pick(exchangeRate, Object.keys(initialValues))),
}, },
onSubmit: (values, { setSubmitting, setErrors }) => { onSubmit: (values, { setSubmitting, setErrors }) => {
if (payload.action === 'edit') { if (payload.action === 'edit') {
@@ -154,9 +150,9 @@ function ExchangeRateDialog({
}, [fetchExchangeRatesDialog]); }, [fetchExchangeRatesDialog]);
const handleDateChange = useCallback( const handleDateChange = useCallback(
(date) => { (date_filed) => (date) => {
const formatted = moment(date).format('YYYY-MM-DD'); const formatted = moment(date).format('YYYY-MM-DD');
setFieldValue('date', formatted); setFieldValue(date_filed, formatted);
}, },
[setFieldValue], [setFieldValue],
); );
@@ -231,10 +227,10 @@ function ExchangeRateDialog({
> >
<DateInput <DateInput
fill={true} fill={true}
{...momentFormatter('YYYY-MM-DD')} {...momentFormatter('YYYY/MM/DD')}
defaultValue={new Date()} value={tansformDateValue(values.date)}
onChange={handleDateChange} onChange={handleDateChange('date')}
popoverProps={{ position: Position.BOTTOM }} popoverProps={{ position: Position.BOTTOM, minimal: true }}
disabled={payload.action === 'edit'} disabled={payload.action === 'edit'}
/> />
</FormGroup> </FormGroup>

View File

@@ -20,12 +20,14 @@ import { connect } from 'react-redux';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import { ListSelect } from 'components'; import { ListSelect, AccountsSelectList } from 'components';
import Dialog from 'components/Dialog'; import Dialog from 'components/Dialog';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import withDialogRedux from 'components/DialogReduxConnect'; import withDialogRedux from 'components/DialogReduxConnect';
import withAccounts from 'containers/Accounts/withAccounts';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withItemCategoryDetail from 'containers/Items/withItemCategoryDetail'; import withItemCategoryDetail from 'containers/Items/withItemCategoryDetail';
import withItemCategories from 'containers/Items/withItemCategories'; import withItemCategories from 'containers/Items/withItemCategories';
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions'; import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
@@ -34,7 +36,7 @@ import Icon from 'components/Icon';
function ItemCategoryDialog({ function ItemCategoryDialog({
dialogName, dialogName,
payload, payload = {},
isOpen, isOpen,
// #withDialog // #withDialog
@@ -48,10 +50,16 @@ function ItemCategoryDialog({
// #withItemCategories // #withItemCategories
categoriesList, categoriesList,
//# withAccount
accountsList,
// #withItemCategoriesActions // #withItemCategoriesActions
requestSubmitItemCategory, requestSubmitItemCategory,
requestFetchItemCategories, requestFetchItemCategories,
requestEditItemCategory, requestEditItemCategory,
// #withAccountsActions
requestFetchAccounts,
}) { }) {
const [selectedParentCategory, setParentCategory] = useState(null); const [selectedParentCategory, setParentCategory] = useState(null);
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@@ -59,13 +67,27 @@ function ItemCategoryDialog({
const fetchList = useQuery(['items-categories-list'], () => const fetchList = useQuery(['items-categories-list'], () =>
requestFetchItemCategories(), requestFetchItemCategories(),
); );
const fetchAccounts = useQuery(
'accounts-list',
() => requestFetchAccounts(),
{ enabled: false },
);
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
name: Yup.string() name: Yup.string()
.required() .required()
.label(formatMessage({ id: 'category_name_' })), .label(formatMessage({ id: 'category_name_' })),
parent_category_id: Yup.string().nullable(), parent_category_id: Yup.string().nullable(),
description: Yup.string().trim(), 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().nullable(),
}); });
const initialValues = useMemo( const initialValues = useMemo(
@@ -73,6 +95,9 @@ function ItemCategoryDialog({
name: '', name: '',
description: '', description: '',
parent_category_id: null, parent_category_id: null,
cost_account_id: null,
sell_account_id: null,
inventory_account_id: null,
}), }),
[], [],
); );
@@ -95,18 +120,21 @@ function ItemCategoryDialog({
}, },
validationSchema, validationSchema,
onSubmit: (values, { setSubmitting }) => { onSubmit: (values, { setSubmitting }) => {
const afterSubmit = () => {
closeDialog(dialogName);
queryCache.invalidateQueries('items-categories-table');
queryCache.invalidateQueries('accounts-list');
};
if (payload.action === 'edit') { if (payload.action === 'edit') {
requestEditItemCategory(payload.id, values) requestEditItemCategory(payload.id, values)
.then((response) => { .then((response) => {
closeDialog(dialogName); afterSubmit(response);
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage({
id: 'the_item_category_has_been_successfully_edited', id: 'the_item_category_has_been_successfully_edited',
}), }),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
setSubmitting(false);
queryCache.invalidateQueries('items-categories-table');
}) })
.catch((error) => { .catch((error) => {
setSubmitting(false); setSubmitting(false);
@@ -114,15 +142,13 @@ function ItemCategoryDialog({
} else { } else {
requestSubmitItemCategory(values) requestSubmitItemCategory(values)
.then((response) => { .then((response) => {
closeDialog(dialogName); afterSubmit(response);
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage({
id: 'the_item_category_has_been_successfully_created', id: 'the_item_category_has_been_successfully_created',
}), }),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
setSubmitting(false);
queryCache.invalidateQueries('items-categories-table');
}) })
.catch((error) => { .catch((error) => {
setSubmitting(false); setSubmitting(false);
@@ -130,7 +156,6 @@ function ItemCategoryDialog({
} }
}, },
}); });
const filterItemCategory = useCallback( const filterItemCategory = useCallback(
(query, category, _index, exactMatch) => { (query, category, _index, exactMatch) => {
const normalizedTitle = category.name.toLowerCase(); const normalizedTitle = category.name.toLowerCase();
@@ -166,7 +191,8 @@ function ItemCategoryDialog({
// Handle the dialog opening. // Handle the dialog opening.
const onDialogOpening = useCallback(() => { const onDialogOpening = useCallback(() => {
fetchList.refetch(); fetchList.refetch();
}, [fetchList]); fetchAccounts.refetch();
}, [fetchList, fetchAccounts]);
const onChangeParentCategory = useCallback( const onChangeParentCategory = useCallback(
(parentCategory) => { (parentCategory) => {
@@ -176,6 +202,15 @@ function ItemCategoryDialog({
[setFieldValue], [setFieldValue],
); );
const onItemAccountSelect = useCallback(
(filedName) => {
return (account) => {
setFieldValue(filedName, account.id);
};
},
[setFieldValue],
);
const onDialogClosed = useCallback(() => { const onDialogClosed = useCallback(() => {
resetForm(); resetForm();
closeDialog(dialogName); closeDialog(dialogName);
@@ -196,14 +231,14 @@ function ItemCategoryDialog({
} }
className={classNames( className={classNames(
{ {
'dialog--loading': fetchList.isFetching, 'dialog--loading': fetchList.isFetching || fetchAccounts.isFetching,
}, },
'dialog--category-form', 'dialog--category-form',
)} )}
isOpen={isOpen} isOpen={isOpen}
onClosed={onDialogClosed} onClosed={onDialogClosed}
onOpening={onDialogOpening} onOpening={onDialogOpening}
isLoading={fetchList.isFetching} isLoading={fetchList.isFetching || fetchAccounts.isFetching}
onClose={handleClose} onClose={handleClose}
> >
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
@@ -273,6 +308,82 @@ function ItemCategoryDialog({
{...getFieldProps('description')} {...getFieldProps('description')}
/> />
</FormGroup> </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_id &&
touched.inventory_account_id &&
Intent.DANGER
}
helperText={
<ErrorMessage
{...{ errors, touched }}
name="inventory_account_id"
/>
}
className={classNames(
'form-group--sell-account',
'form-group--select-list',
Classes.FILL,
)}
>
<AccountsSelectList
accounts={accountsList}
onAccountSelected={onItemAccountSelect('inventory_account_id')}
defaultSelectText={<T id={'select_account'} />}
selectedAccountId={values.inventory_account_id}
/>
</FormGroup>
</div> </div>
<div className={Classes.DIALOG_FOOTER}> <div className={Classes.DIALOG_FOOTER}>
@@ -299,7 +410,8 @@ function ItemCategoryDialog({
} }
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => ({
itemCategoryId: props?.dialogPayload?.id || null, dialogName: 'item-category-form',
itemCategoryId: props?.payload?.id || null,
}); });
const withItemCategoryDialog = connect(mapStateToProps); const withItemCategoryDialog = connect(mapStateToProps);
@@ -312,5 +424,9 @@ export default compose(
withItemCategories(({ categoriesList }) => ({ withItemCategories(({ categoriesList }) => ({
categoriesList, categoriesList,
})), })),
withAccounts(({ accountsList }) => ({
accountsList,
})),
withItemCategoriesActions, withItemCategoriesActions,
withAccountsActions,
)(ItemCategoryDialog); )(ItemCategoryDialog);

View File

@@ -20,7 +20,7 @@ import FilterDropdown from 'components/FilterDropdown';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import withResourceDetail from 'containers/Resources/withResourceDetails'; import withResourceDetail from 'containers/Resources/withResourceDetails';
import withExchangeRatesActions from './withExchangeRatesActions';
import { compose } from 'utils'; import { compose } from 'utils';
/** /**
@@ -33,6 +33,9 @@ function ExchangeRateActionsBar({
// #withResourceDetail // #withResourceDetail
resourceFields, resourceFields,
//#withExchangeRatesActions
addExchangeRatesTableQueries,
// #ownProps // #ownProps
selectedRows = [], selectedRows = [],
onDeleteExchangeRate, onDeleteExchangeRate,
@@ -46,14 +49,20 @@ function ExchangeRateActionsBar({
openDialog('exchangeRate-form', {}); openDialog('exchangeRate-form', {});
}; };
const filterDropdown = FilterDropdown({ // const filterDropdown = FilterDropdown({
fields: resourceFields, // initialCondition: {
onFilterChange: (filterConditions) => { // fieldKey: '',
setFilterCount(filterConditions.length || 0); // compatator: 'contains',
// value: '',
onFilterChanged && onFilterChanged(filterConditions); // },
}, // fields: resourceFields,
}); // onFilterChange: (filterConditions) => {
// addExchangeRatesTableQueries({
// filter_roles: filterConditions || '',
// });
// onFilterChanged && onFilterChanged(filterConditions);
// },
// });
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [ const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
selectedRows, selectedRows,
@@ -76,16 +85,18 @@ function ExchangeRateActionsBar({
<Popover <Popover
minimal={true} minimal={true}
content={filterDropdown} // content={filterDropdown}
interactionKind={PopoverInteractionKind.CLICK} interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT} position={Position.BOTTOM_LEFT}
> >
<Button <Button
className={classNames(Classes.MINIMAL, 'button--filter')} className={classNames(Classes.MINIMAL, 'button--filter')}
text={ text={
filterCount <= 0 ? filterCount <= 0 ? (
(<T id={'filter'} />) : <T id={'filter'} />
(`${filterCount} ${formatMessage({ id: 'filters_applied' })}`) ) : (
`${filterCount} ${formatMessage({ id: 'filters_applied' })}`
)
} }
icon={<Icon icon="filter-16" iconSize={16} />} icon={<Icon icon="filter-16" iconSize={16} />}
/> />
@@ -117,7 +128,7 @@ function ExchangeRateActionsBar({
} }
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => ({
resourceName: 'exchange_rates', resourceName: '',
}); });
const withExchangeRateActionBar = connect(mapStateToProps); const withExchangeRateActionBar = connect(mapStateToProps);
@@ -128,4 +139,5 @@ export default compose(
withResourceDetail(({ resourceFields }) => ({ withResourceDetail(({ resourceFields }) => ({
resourceFields, resourceFields,
})), })),
withExchangeRatesActions,
)(ExchangeRateActionsBar); )(ExchangeRateActionsBar);

View File

@@ -1,9 +1,16 @@
import React, { useCallback, useMemo, useState, useEffect } from 'react'; import React, { useCallback, useMemo, useState, useEffect } from 'react';
import { Button, Popover, Menu, MenuItem, Position,Intent } from '@blueprintjs/core'; import {
Button,
Popover,
Menu,
MenuItem,
Position,
Intent,
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import Icon from 'components/Icon'; import { DataTable, Money, Icon } from 'components';
import DataTable from 'components/DataTable';
import LoadingIndicator from 'components/LoadingIndicator'; import LoadingIndicator from 'components/LoadingIndicator';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -30,10 +37,12 @@ function ExchangeRateTable({
const [initialMount, setInitialMount] = useState(false); const [initialMount, setInitialMount] = useState(false);
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const handelEditExchangeRate = (exchange_rate) => () => { const handelEditExchangeRate = useCallback(
openDialog('exchangeRate-form', { action: 'edit', id: exchange_rate.id }); (exchange_rate) => () => {
onEditExchangeRate(exchange_rate.id); openDialog('exchangeRate-form', { action: 'edit', id: exchange_rate.id });
}; },
[openDialog],
);
const handleDeleteExchangeRate = (exchange_rate) => () => { const handleDeleteExchangeRate = (exchange_rate) => () => {
onDeleteExchangeRate(exchange_rate); onDeleteExchangeRate(exchange_rate);
@@ -50,49 +59,53 @@ function ExchangeRateTable({
text={<T id={'delete_exchange_rate'} />} text={<T id={'delete_exchange_rate'} />}
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={handleDeleteExchangeRate(ExchangeRate)} onClick={handleDeleteExchangeRate(ExchangeRate)}
icon={<Icon icon="trash-16" iconSize={16} />}
/> />
</Menu> </Menu>
), ),
[handelEditExchangeRate, handleDeleteExchangeRate], [handelEditExchangeRate, handleDeleteExchangeRate],
); );
const columns = useMemo(() => [ const columns = useMemo(
{ () => [
id: 'date', {
Header: formatMessage({ id: 'date' }), id: 'date',
// accessor: 'date', Header: formatMessage({ id: 'date' }),
width: 150, accessor: (r) => moment(r.date).format('YYYY MMM DD'),
}, width: 150,
{ },
id: 'currency_code', {
Header: formatMessage({ id: 'currency_code' }), id: 'currency_code',
accessor: 'currency_code', Header: formatMessage({ id: 'currency_code' }),
className: 'currency_code', accessor: 'currency_code',
width: 150, className: 'currency_code',
}, width: 150,
{ },
id: 'exchange_rate', {
Header: formatMessage({ id: 'exchange_rate' }), id: 'exchange_rate',
accessor: 'exchange_rate', Header: formatMessage({ id: 'exchange_rate' }),
className: 'exchange_rate', accessor: (r) => <Money amount={r.exchange_rate} currency={'USD'} />,
width: 150, className: 'exchange_rate',
}, width: 150,
{ },
id: 'actions', {
Header: '', id: 'actions',
Cell: ({ cell }) => ( Header: '',
<Popover Cell: ({ cell }) => (
content={actionMenuList(cell.row.original)} <Popover
position={Position.RIGHT_BOTTOM} content={actionMenuList(cell.row.original)}
> position={Position.RIGHT_BOTTOM}
<Button icon={<Icon icon='more-h-16' iconSize={16} />} /> >
</Popover> <Button icon={<Icon icon="more-h-16" iconSize={16} />} />
), </Popover>
className: 'actions', ),
width: 50, className: 'actions',
disableResizing: false, width: 50,
}, disableResizing: false,
], [actionMenuList,formatMessage]); },
],
[actionMenuList, formatMessage],
);
const selectionColumn = useMemo( const selectionColumn = useMemo(
() => ({ () => ({
@@ -120,18 +133,18 @@ function ExchangeRateTable({
return ( return (
<LoadingIndicator loading={loading} mount={false}> <LoadingIndicator loading={loading} mount={false}>
<DataTable <DataTable
columns={columns} columns={columns}
data={exchangeRatesList} data={exchangeRatesList}
onFetchData={handelFetchData} onFetchData={handelFetchData}
loading={exchangeRatesLoading && !initialMount} loading={exchangeRatesLoading && !initialMount}
manualSortBy={true} manualSortBy={true}
selectionColumn={selectionColumn} selectionColumn={selectionColumn}
expandable={true} expandable={true}
treeGraph={true} treeGraph={true}
onSelectedRowsChange={handelSelectedRowsChange} onSelectedRowsChange={handelSelectedRowsChange}
spinnerProps={{ size: 30 }} spinnerProps={{ size: 30 }}
/> />
</LoadingIndicator> </LoadingIndicator>
); );
} }

View File

@@ -0,0 +1,8 @@
import { connect } from 'react-redux';
import { getExchangeRateById } from 'store/ExchangeRate/exchange.selector';
const mapStateToProps = (state, props) => ({
exchangeRate: getExchangeRateById(state, props),
});
export default connect(mapStateToProps);

View File

@@ -14,6 +14,7 @@ import ItemCategoriesDataTable from 'containers/Items/ItemCategoriesTable';
import ItemsCategoryActionsBar from 'containers/Items/ItemsCategoryActionsBar'; import ItemsCategoryActionsBar from 'containers/Items/ItemsCategoryActionsBar';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import withResourceActions from 'containers/Resources/withResourcesActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions'; import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
import { compose } from 'utils'; import { compose } from 'utils';
@@ -25,10 +26,15 @@ const ItemCategoryList = ({
// #withDashboardActions // #withDashboardActions
changePageTitle, changePageTitle,
// #withViewsActions
requestFetchResourceViews,
requestFetchResourceFields,
// #withItemCategoriesActions // #withItemCategoriesActions
requestFetchItemCategories, requestFetchItemCategories,
requestDeleteItemCategory, requestDeleteItemCategory,
requestDeleteBulkItemCategories, requestDeleteBulkItemCategories,
addItemCategoriesTableQueries,
// #withDialog // #withDialog
openDialog, openDialog,
@@ -48,9 +54,13 @@ const ItemCategoryList = ({
: changePageTitle(formatMessage({ id: 'category_list' })); : changePageTitle(formatMessage({ id: 'category_list' }));
}, [id, changePageTitle, formatMessage]); }, [id, changePageTitle, formatMessage]);
const fetchCategories = useQuery( const fetchCategories = useQuery(['items-categories-list'], () =>
['items-categories-list', filter], requestFetchItemCategories(),
(key, query) => requestFetchItemCategories(query), );
const fetchResourceFields = useQuery(
['resource-fields', 'items_categories'],
(key, resourceName) => requestFetchResourceFields(resourceName),
); );
const handleFilterChanged = useCallback(() => {}, []); const handleFilterChanged = useCallback(() => {}, []);
@@ -63,17 +73,23 @@ const ItemCategoryList = ({
[setSelectedRows], [setSelectedRows],
); );
// Handle fetch data of accounts datatable. const handleFetchData = useCallback(
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => { ({ pageIndex, pageSize, sortBy }) => {
setFilter({ const page = pageIndex + 1;
...(sortBy.length > 0
? { addItemCategoriesTableQueries({
column_sort_by: sortBy[0].id, ...(sortBy.length > 0
sort_order: sortBy[0].desc ? 'desc' : 'asc', ? {
} column_sort_by: sortBy[0].id,
: {}), sort_order: sortBy[0].desc ? 'desc' : 'asc',
}); }
}, []); : {}),
page_size: pageSize,
page,
});
},
[addItemCategoriesTableQueries],
);
const handleDeleteCategory = (itemCategory) => { const handleDeleteCategory = (itemCategory) => {
setDeleteCategory(itemCategory); setDeleteCategory(itemCategory);
@@ -139,7 +155,10 @@ const ItemCategoryList = ({
]); ]);
return ( return (
<DashboardInsider name={'item-category-list'}> <DashboardInsider
loading={fetchResourceFields.isFetching}
name={'item-category-list'}
>
<ItemsCategoryActionsBar <ItemsCategoryActionsBar
selectedRows={selectedRows} selectedRows={selectedRows}
onFilterChanged={handleFilterChanged} onFilterChanged={handleFilterChanged}
@@ -151,7 +170,7 @@ const ItemCategoryList = ({
onFetchData={handleFetchData} onFetchData={handleFetchData}
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
onDeleteCategory={handleDeleteCategory} onDeleteCategory={handleDeleteCategory}
loading={tableLoading} loading={fetchCategories.isFetching}
/> />
<Alert <Alert
@@ -197,4 +216,5 @@ export default compose(
withItemCategoriesActions, withItemCategoriesActions,
withDashboardActions, withDashboardActions,
withDialogActions, withDialogActions,
withResourceActions,
)(ItemCategoryList); )(ItemCategoryList);

View File

@@ -16,11 +16,15 @@ import { compose } from 'utils';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import withItemCategories from './withItemCategories'; import withItemCategories from './withItemCategories';
import withDialogActions from 'containers/Dialog/withDialogActions';
const ItemsCategoryList = ({ const ItemsCategoryList = ({
// #withItemCategories // #withItemCategories
categoriesList, categoriesList,
// #withDialogActions.
openDialog,
// #ownProps // #ownProps
onFetchData, onFetchData,
onDeleteCategory, onDeleteCategory,
@@ -30,11 +34,12 @@ const ItemsCategoryList = ({
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const handelEditCategory = useCallback( const handelEditCategory = useCallback(
(category) => { (category) => () => {
onEditCategory(category); openDialog('item-category-form', { action: 'edit', id: category.id });
}, },
[onEditCategory], [],
); );
const handleDeleteCategory = useCallback( const handleDeleteCategory = useCallback(
(category) => { (category) => {
onDeleteCategory(category); onDeleteCategory(category);
@@ -46,13 +51,14 @@ const ItemsCategoryList = ({
<Menu> <Menu>
<MenuItem <MenuItem
text={formatMessage({ id: 'edit_category' })} text={formatMessage({ id: 'edit_category' })}
onClick={() => handelEditCategory(category)} onClick={handelEditCategory(category)}
/> />
<MenuDivider /> <MenuDivider />
<MenuItem <MenuItem
text={formatMessage({ id: 'delete_category' })} text={formatMessage({ id: 'delete_category' })}
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={() => handleDeleteCategory(category)} onClick={() => handleDeleteCategory(category)}
icon={<Icon icon="trash-16" iconSize={16} />}
/> />
</Menu> </Menu>
), ),
@@ -77,7 +83,7 @@ const ItemsCategoryList = ({
{ {
id: 'count', id: 'count',
Header: formatMessage({ id: 'count' }), Header: formatMessage({ id: 'count' }),
accessor: (r) => r.count || '', accessor: 'count',
className: 'count', className: 'count',
width: 50, width: 50,
}, },
@@ -154,4 +160,5 @@ export default compose(
withItemCategories(({ categoriesList }) => ({ withItemCategories(({ categoriesList }) => ({
categoriesList, categoriesList,
})), })),
withDialogActions,
)(ItemsCategoryList); )(ItemsCategoryList);

View File

@@ -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 * as Yup from 'yup';
import { useFormik } from 'formik'; import { useFormik, Formik } from 'formik';
import { import {
FormGroup, FormGroup,
MenuItem, MenuItem,
@@ -23,29 +23,28 @@ import ErrorMessage from 'components/ErrorMessage';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import MoneyInputGroup from 'components/MoneyInputGroup'; import MoneyInputGroup from 'components/MoneyInputGroup';
import Dragzone from 'components/Dragzone'; import Dragzone from 'components/Dragzone';
import { ListSelect } from 'components'; import { ListSelect, AccountsSelectList, If } from 'components';
import withItemsActions from 'containers/Items/withItemsActions'; import withItemsActions from 'containers/Items/withItemsActions';
import withItemCategories from 'containers/Items/withItemCategories' import withItemCategories from 'containers/Items/withItemCategories';
import withAccounts from 'containers/Accounts/withAccounts'; import withAccounts from 'containers/Accounts/withAccounts';
import withMediaActions from 'containers/Media/withMediaActions'; import withMediaActions from 'containers/Media/withMediaActions';
import useMedia from 'hooks/useMedia'; import useMedia from 'hooks/useMedia';
import withItemDetail from 'containers/Items/withItemDetail' import withItemDetail from 'containers/Items/withItemDetail';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withAccountDetail from 'containers/Accounts/withAccountDetail'; import withAccountDetail from 'containers/Accounts/withAccountDetail';
import { compose } from 'utils'; import { compose } from 'utils';
const ItemForm = ({ const ItemForm = ({
// #withItemActions // #withItemActions
requestSubmitItem, requestSubmitItem,
requestEditItem, requestEditItem,
accounts, accountsList,
itemDetail, itemDetail,
onFormSubmit, onFormSubmit,
onCancelForm, onCancelForm,
// #withDashboardActions // #withDashboardActions
changePageTitle, changePageTitle,
@@ -55,10 +54,10 @@ const ItemForm = ({
// #withMediaActions // #withMediaActions
requestSubmitMedia, requestSubmitMedia,
requestDeleteMedia, requestDeleteMedia,
}) => { }) => {
const [payload, setPayload] = useState({}); const [payload, setPayload] = useState({});
const history = useHistory(); const history = useHistory();
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { const {
@@ -72,22 +71,34 @@ const ItemForm = ({
deleteCallback: requestDeleteMedia, deleteCallback: requestDeleteMedia,
}); });
const ItemTypeDisplay = useMemo(() => [ const ItemTypeDisplay = useMemo(
{ value: null, label: formatMessage({id:'select_item_type'}) }, () => [
{ value: 'service', label: formatMessage({id:'service'}) }, { value: null, label: formatMessage({ id: 'select_item_type' }) },
{ value: 'inventory', label: formatMessage({id:'inventory'}) }, { value: 'service', label: formatMessage({ id: 'service' }) },
{ value: 'non-inventory', label: formatMessage({id:'non_inventory'}) }, { value: 'inventory', label: formatMessage({ id: 'inventory' }) },
], [formatMessage]); { value: 'non-inventory', label: formatMessage({ id: 'non_inventory' }) },
],
[formatMessage],
);
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
active: Yup.boolean(), active: Yup.boolean(),
name: Yup.string().required().label(formatMessage({id:'item_name_'})), name: Yup.string()
type: Yup.string().trim().required().label(formatMessage({id:'item_type_'})), .required()
.label(formatMessage({ id: 'item_name_' })),
type: Yup.string()
.trim()
.required()
.label(formatMessage({ id: 'item_type_' })),
sku: Yup.string().trim(), sku: Yup.string().trim(),
cost_price: Yup.number(), cost_price: Yup.number(),
sell_price: Yup.number(), sell_price: Yup.number(),
cost_account_id: Yup.number().required().label(formatMessage({id:'cost_account_id'})), cost_account_id: Yup.number()
sell_account_id: Yup.number().required().label(formatMessage({id:'sell_account_id'})), .required()
.label(formatMessage({ id: 'cost_account_id' })),
sell_account_id: Yup.number()
.required()
.label(formatMessage({ id: 'sell_account_id' })),
inventory_account_id: Yup.number().when('type', { inventory_account_id: Yup.number().when('type', {
is: (value) => value === 'inventory', is: (value) => value === 'inventory',
then: Yup.number().required(), then: Yup.number().required(),
@@ -95,6 +106,8 @@ const ItemForm = ({
}), }),
category_id: Yup.number().nullable(), category_id: Yup.number().nullable(),
stock: Yup.string() || Yup.boolean(), stock: Yup.string() || Yup.boolean(),
sellable: Yup.boolean().required(),
purchasable: Yup.boolean().required(),
}); });
const defaultInitialValues = useMemo( const defaultInitialValues = useMemo(
@@ -110,27 +123,36 @@ const ItemForm = ({
inventory_account_id: null, inventory_account_id: null,
category_id: null, category_id: null,
note: '', 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) => { const saveInvokeSubmit = useCallback(
onFormSubmit && onFormSubmit(payload) (payload) => {
}, [onFormSubmit]); onFormSubmit && onFormSubmit(payload);
},
[onFormSubmit],
);
useEffect(() => { useEffect(() => {
itemDetail && itemDetail.id ? itemDetail && itemDetail.id
changePageTitle(formatMessage({id:'edit_item_details'})) : ? changePageTitle(formatMessage({ id: 'edit_item_details' }))
changePageTitle(formatMessage({id:'new_item'})); : changePageTitle(formatMessage({ id: 'new_item' }));
}, [changePageTitle,itemDetail,formatMessage]); }, [changePageTitle, itemDetail, formatMessage]);
const { const {
getFieldProps, getFieldProps,
@@ -146,55 +168,56 @@ const ItemForm = ({
initialValues: { initialValues: {
...initialValues, ...initialValues,
}, },
onSubmit: (values, { setSubmitting,resetForm,setErrors }) => { onSubmit: (values, { setSubmitting, resetForm, setErrors }) => {
const saveItem = (mediaIds) => { const saveItem = (mediaIds) => {
const formValues = { ...values, media_ids: mediaIds }; const formValues = { ...values, media_ids: mediaIds };
if(itemDetail && itemDetail.id ){ if (itemDetail && itemDetail.id) {
requestEditItem(itemDetail.id, formValues)
requestEditItem(itemDetail.id,formValues) .then((response) => {
.then((response)=>{ AppToaster.show({
AppToaster.show({ message: formatMessage(
message:formatMessage({ {
id:'the_item_has_been_successfully_edited', id: 'the_item_has_been_successfully_edited',
},{ },
number:itemDetail.id {
}), number: itemDetail.id,
intent:Intent.SUCCESS },
),
intent: Intent.SUCCESS,
});
setSubmitting(false);
saveInvokeSubmit({ action: 'update', ...payload });
history.push('/items');
resetForm();
})
.catch((errors) => {
setSubmitting(false);
}); });
setSubmitting(false); } else {
saveInvokeSubmit({action:'update',...payload})
history.push('/items');
resetForm();
}).catch((errors)=>{
setSubmitting(false)
});
}else{
requestSubmitItem(formValues).then((response) => { requestSubmitItem(formValues).then((response) => {
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage(
id: 'service_has_been_successful_created', {
}, { id: 'service_has_been_successful_created',
name: values.name, },
service: formatMessage({ id: 'item' }), {
}), name: values.name,
service: formatMessage({ id: 'item' }),
},
),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
queryCache.removeQueries(['items-table']); queryCache.removeQueries(['items-table']);
history.push('/items'); history.push('/items');
}); });
};
} }
};
Promise.all([saveMedia(), deleteMedia()]).then( Promise.all([saveMedia(), deleteMedia()]).then(
([savedMediaResponses]) => { ([savedMediaResponses]) => {
const mediaIds = savedMediaResponses.map((res) => res.data.media.id); const mediaIds = savedMediaResponses.map((res) => res.data.media.id);
return saveItem(mediaIds); return saveItem(mediaIds);
} },
); );
}, },
}); });
@@ -208,53 +231,52 @@ const ItemForm = ({
onClick={handleClick} onClick={handleClick}
/> />
), ),
[] [],
); );
// Filter Account Items // Filter Account Items
const filterAccounts = (query, account, _index, exactMatch) => { const filterAccounts = (query, item, _index, exactMatch) => {
const normalizedTitle = account.name.toLowerCase(); const normalizedTitle = item.name.toLowerCase();
const normalizedQuery = query.toLowerCase(); const normalizedQuery = query.toLowerCase();
if (exactMatch) { if (exactMatch) {
return normalizedTitle === normalizedQuery; return normalizedTitle === normalizedQuery;
} else { } else {
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0; return `${item.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
} }
}; };
const onItemAccountSelect = useCallback((filedName) => { const onItemAccountSelect = useCallback(
return (account) => { (filedName) => {
setFieldValue(filedName, account.id); return (account) => {
}; setFieldValue(filedName, account.id);
}, [setFieldValue]); };
},
[setFieldValue],
);
const categoryItem = useCallback( const categoryItem = useCallback(
(item, { handleClick }) => ( (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 requiredSpan = useMemo(() => <span class="required">*</span>, []);
const infoIcon = useMemo(() => <Icon icon='info-circle' iconSize={12} />, []); const infoIcon = useMemo(() => <Icon icon="info-circle" iconSize={12} />, []);
const handleMoneyInputChange = (fieldKey) => (e, value) => { const handleMoneyInputChange = (fieldKey) => (e, value) => {
setFieldValue(fieldKey, value); setFieldValue(fieldKey, value);
}; };
const initialAttachmentFiles = useMemo(() => {
const initialAttachmentFiles =useMemo(()=>{
return itemDetail && itemDetail.media return itemDetail && itemDetail.media
? itemDetail.media.map((attach)=>({ ? itemDetail.media.map((attach) => ({
preview: attach.attachment_file,
preview:attach.attachment_file, upload: true,
upload:true, metadata: { ...attach },
metadata:{...attach} }))
: [];
})):[]; }, [itemDetail]);
},[itemDetail])
const handleDropFiles = useCallback((_files) => { const handleDropFiles = useCallback((_files) => {
setFiles(_files.filter((file) => file.uploaded === false)); setFiles(_files.filter((file) => file.uploaded === false));
}, []); }, []);
@@ -267,7 +289,7 @@ const ItemForm = ({
} }
}); });
}, },
[setDeletedFiles, deletedFiles,] [setDeletedFiles, deletedFiles],
); );
const handleCancelClickBtn = () => { const handleCancelClickBtn = () => {
@@ -275,12 +297,11 @@ const ItemForm = ({
}; };
return ( return (
<div class='item-form'> <div class="item-form">
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div class='item-form__primary-section'> <div class="item-form__primary-section">
<Row> <Row>
<Col xs={7}> <Col xs={7}>
{/* Item type */} {/* Item type */}
<FormGroup <FormGroup
medium={true} medium={true}
@@ -289,7 +310,7 @@ const ItemForm = ({
className={'form-group--item-type'} className={'form-group--item-type'}
intent={errors.type && touched.type && Intent.DANGER} intent={errors.type && touched.type && Intent.DANGER}
helperText={ helperText={
<ErrorMessage {...{ errors, touched }} name='type' /> <ErrorMessage {...{ errors, touched }} name="type" />
} }
inline={true} inline={true}
> >
@@ -299,7 +320,7 @@ const ItemForm = ({
{...getFieldProps('type')} {...getFieldProps('type')}
/> />
</FormGroup> </FormGroup>
{/* Item name */} {/* Item name */}
<FormGroup <FormGroup
label={<T id={'item_name'} />} label={<T id={'item_name'} />}
@@ -307,7 +328,7 @@ const ItemForm = ({
className={'form-group--item-name'} className={'form-group--item-name'}
intent={errors.name && touched.name && Intent.DANGER} intent={errors.name && touched.name && Intent.DANGER}
helperText={ helperText={
<ErrorMessage {...{ errors, touched }} name='name' /> <ErrorMessage {...{ errors, touched }} name="name" />
} }
inline={true} inline={true}
> >
@@ -324,7 +345,9 @@ const ItemForm = ({
labelInfo={infoIcon} labelInfo={infoIcon}
className={'form-group--item-sku'} className={'form-group--item-sku'}
intent={errors.sku && touched.sku && Intent.DANGER} intent={errors.sku && touched.sku && Intent.DANGER}
helperText={<ErrorMessage {...{ errors, touched }} name='sku' />} helperText={
<ErrorMessage {...{ errors, touched }} name="sku" />
}
inline={true} inline={true}
> >
<InputGroup <InputGroup
@@ -343,29 +366,28 @@ const ItemForm = ({
errors.category_id && touched.category_id && Intent.DANGER errors.category_id && touched.category_id && Intent.DANGER
} }
helperText={ helperText={
<ErrorMessage {...{ errors, touched }} name='category' /> <ErrorMessage {...{ errors, touched }} name="category" />
} }
className={classNames( className={classNames(
'form-group--select-list', 'form-group--select-list',
'form-group--category', 'form-group--category',
Classes.FILL Classes.FILL,
)} )}
> >
<ListSelect <ListSelect
items={categoriesList} items={categoriesList}
noResults={<MenuItem disabled={true} text="No results." />}
itemRenderer={categoryItem} itemRenderer={categoryItem}
itemPredicate={filterAccounts} itemPredicate={filterAccounts}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
onItemSelect={onItemAccountSelect('category_id')} onItemSelect={onItemAccountSelect('category_id')}
selectedItem={values.customer_id}
selectedItem={values.category_id}
selectedItemProp={'id'} selectedItemProp={'id'}
defaultText={<T id={'select_category'} />} defaultText={<T id={'select_category'} />}
labelProp={'name'} labelProp={'name'}
/> />
</FormGroup> </FormGroup>
{/* Active checkbox */} {/* Active checkbox */}
<FormGroup <FormGroup
label={' '} label={' '}
@@ -374,7 +396,7 @@ const ItemForm = ({
> >
<Checkbox <Checkbox
inline={true} inline={true}
label={<T id={'active'}/>} label={<T id={'active'} />}
defaultChecked={values.active} defaultChecked={values.active}
{...getFieldProps('active')} {...getFieldProps('active')}
/> />
@@ -395,14 +417,18 @@ const ItemForm = ({
<Row gutterWidth={16} className={'item-form__accounts-section'}> <Row gutterWidth={16} className={'item-form__accounts-section'}>
<Col width={404}> <Col width={404}>
<h4><T id={'purchase_information'}/></h4> <h4>
<T id={'sales_information'} />
</h4>
<FormGroup <FormGroup
label={<T id={'selling_price'}/>} label={<T id={'selling_price'} />}
className={'form-group--item-selling-price'} className={'form-group--item-selling-price'}
intent={errors.selling_price && touched.selling_price && Intent.DANGER} intent={
errors.selling_price && touched.selling_price && Intent.DANGER
}
helperText={ helperText={
<ErrorMessage {...{ errors, touched }} name='selling_price' /> <ErrorMessage {...{ errors, touched }} name="selling_price" />
} }
inline={true} inline={true}
> >
@@ -417,9 +443,10 @@ const ItemForm = ({
touched.selling_price && touched.selling_price &&
Intent.DANGER, Intent.DANGER,
}} }}
disabled={!values.sellable}
/> />
</FormGroup> </FormGroup>
{/* Selling account */} {/* Selling account */}
<FormGroup <FormGroup
label={<T id={'account'} />} label={<T id={'account'} />}
@@ -431,32 +458,42 @@ const ItemForm = ({
Intent.DANGER Intent.DANGER
} }
helperText={ helperText={
<ErrorMessage {...{ errors, touched }} name='sell_account_id' /> <ErrorMessage {...{ errors, touched }} name="sell_account_id" />
} }
className={classNames( className={classNames(
'form-group--sell-account', 'form-group--sell-account',
'form-group--select-list', 'form-group--select-list',
Classes.FILL Classes.FILL,
)} )}
> >
<ListSelect <AccountsSelectList
items={accounts} accounts={accountsList}
itemRenderer={accountItem} onAccountSelected={onItemAccountSelect('sell_account_id')}
itemPredicate={filterAccounts} defaultSelectText={<T id={'select_account'} />}
popoverProps={{ minimal: true }} selectedAccountId={values.sell_account_id}
onItemSelect={onItemAccountSelect('sell_account_id')} disabled={!values.sellable}
/>
</FormGroup>
selectedItem={values.sell_account_id} {/* sellable checkbox */}
selectedItemProp={'id'} <FormGroup
label={' '}
defaultText={<T id={'select_account'} />} inline={true}
labelProp={'name'} className={'form-group--sellable'}
>
<Checkbox
inline={true}
label={<T id={'sellable'} />}
checked={values.sellable}
{...getFieldProps('sellable')}
/> />
</FormGroup> </FormGroup>
</Col> </Col>
<Col width={404}> <Col width={404}>
<h4><T id={'sales_information'} /></h4> <h4>
<T id={'purchase_information'} />
</h4>
{/* Cost price */} {/* Cost price */}
<FormGroup <FormGroup
@@ -464,7 +501,7 @@ const ItemForm = ({
className={'form-group--item-cost-price'} className={'form-group--item-cost-price'}
intent={errors.cost_price && touched.cost_price && Intent.DANGER} intent={errors.cost_price && touched.cost_price && Intent.DANGER}
helperText={ helperText={
<ErrorMessage {...{ errors, touched }} name='cost_price' /> <ErrorMessage {...{ errors, touched }} name="cost_price" />
} }
inline={true} inline={true}
> >
@@ -477,6 +514,7 @@ const ItemForm = ({
intent: intent:
errors.cost_price && touched.cost_price && Intent.DANGER, errors.cost_price && touched.cost_price && Intent.DANGER,
}} }}
disabled={!values.purchasable}
/> />
</FormGroup> </FormGroup>
@@ -490,25 +528,34 @@ const ItemForm = ({
Intent.DANGER Intent.DANGER
} }
helperText={ helperText={
<ErrorMessage {...{ errors, touched }} name='cost_account_id' /> <ErrorMessage {...{ errors, touched }} name="cost_account_id" />
} }
className={classNames( className={classNames(
'form-group--cost-account', 'form-group--cost-account',
'form-group--select-list', 'form-group--select-list',
Classes.FILL Classes.FILL,
)} )}
> >
<ListSelect <AccountsSelectList
items={accounts} accounts={accountsList}
itemRenderer={accountItem} onAccountSelected={onItemAccountSelect('cost_account_id')}
itemPredicate={filterAccounts} defaultSelectText={<T id={'select_account'} />}
popoverProps={{ minimal: true }} selectedAccountId={values.cost_account_id}
onItemSelect={onItemAccountSelect('cost_account_id')} disabled={!values.purchasable}
/>
</FormGroup>
defaultText={<T id={'select_account'} />} {/* purchasable checkbox */}
labelProp={'name'} <FormGroup
selectedItem={values.cost_account_id} label={' '}
selectedItemProp={'id'} inline={true}
className={'form-group--purchasable'}
>
<Checkbox
inline={true}
label={<T id={'purchasable'} />}
defaultChecked={values.purchasable}
{...getFieldProps('purchasable')}
/> />
</FormGroup> </FormGroup>
</Col> </Col>
@@ -521,35 +568,35 @@ const ItemForm = ({
</h4> </h4>
<FormGroup <FormGroup
label={<T id={'inventory_account'}/>} label={<T id={'inventory_account'} />}
inline={true} inline={true}
intent={ intent={
errors.inventory_account_id && errors.inventory_account_id &&
touched.inventory_account_id && touched.inventory_account_id &&
Intent.DANGER Intent.DANGER
} }
helperText={<ErrorMessage {...{ errors, touched }} name='inventory_account_id' />} helperText={
<ErrorMessage
{...{ errors, touched }}
name="inventory_account_id"
/>
}
className={classNames( className={classNames(
'form-group--item-inventory_account', 'form-group--item-inventory_account',
'form-group--select-list', 'form-group--select-list',
Classes.FILL Classes.FILL,
)} )}
> >
<ListSelect <AccountsSelectList
items={accounts} accounts={accountsList}
itemRenderer={accountItem} onAccountSelected={onItemAccountSelect('inventory_account_id')}
itemPredicate={filterAccounts} defaultSelectText={<T id={'select_account'} />}
popoverProps={{ minimal: true }} selectedAccountId={values.inventory_account_id}
onItemSelect={onItemAccountSelect('inventory_account_id')} />
defaultText={<T id={'select_account'} />}
labelProp={'name'}
selectedItem={values.inventory_account_id}
selectedItemProp={'id'} />
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={<T id={'opening_stock'}/>} label={<T id={'opening_stock'} />}
className={'form-group--item-stock'} className={'form-group--item-stock'}
inline={true} inline={true}
> >
@@ -562,14 +609,17 @@ const ItemForm = ({
</Col> </Col>
</Row> </Row>
<div class='form__floating-footer'> <div class="form__floating-footer">
<Button intent={Intent.PRIMARY} disabled={isSubmitting} type='submit'> <Button intent={Intent.PRIMARY} disabled={isSubmitting} type="submit">
{itemDetail && itemDetail.id ? (
{ itemDetail && itemDetail.id ? <T id={'edit'}/> : <T id={'save'}/> } <T id={'edit'} />
) : (
<T id={'save'} />
)}
</Button> </Button>
<Button className={'ml1'} disabled={isSubmitting}> <Button className={'ml1'} disabled={isSubmitting}>
<T id={'save_as_draft'}/> <T id={'save_as_draft'} />
</Button> </Button>
<Button className={'ml1'} onClick={handleCancelClickBtn}> <Button className={'ml1'} onClick={handleCancelClickBtn}>
@@ -582,8 +632,8 @@ const ItemForm = ({
}; };
export default compose( export default compose(
withAccounts(({accounts})=>({ withAccounts(({ accountsList }) => ({
accounts, accountsList,
})), })),
withAccountDetail, withAccountDetail,
withItemsActions, withItemsActions,

View File

@@ -23,9 +23,10 @@ import { If } from 'components';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import withResourceDetail from 'containers/Resources/withResourceDetails'; import withResourceDetail from 'containers/Resources/withResourceDetails';
import withItems from 'containers/Items/withItems'; import withItems from 'containers/Items/withItems';
import withItemsActions from './withItemsActions';
import { compose } from 'utils'; import { compose } from 'utils';
import { connect } from 'react-redux';
const ItemsActionsBar = ({ const ItemsActionsBar = ({
openDialog, openDialog,
@@ -36,6 +37,9 @@ const ItemsActionsBar = ({
// #withItems // #withItems
itemsViews, itemsViews,
//#withItemActions
addItemsTableQueries,
onFilterChanged, onFilterChanged,
selectedRows = [], selectedRows = [],
onBulkDelete, onBulkDelete,
@@ -56,10 +60,26 @@ const ItemsActionsBar = ({
selectedRows, selectedRows,
]); ]);
// name
// const filterDropdown = FilterDropdown({
// fields: resourceFields,
// onFilterChange: (filterConditions) => {
// setFilterCount(filterConditions.length);
// onFilterChanged && onFilterChanged(filterConditions);
// },
// });
const filterDropdown = FilterDropdown({ const filterDropdown = FilterDropdown({
initialCondition: {
fieldKey: 'name',
compatator: 'contains',
value: '',
},
fields: resourceFields, fields: resourceFields,
onFilterChange: (filterConditions) => { onFilterChange: (filterConditions) => {
setFilterCount(filterConditions.length); addItemsTableQueries({
filter_roles: filterConditions || '',
});
onFilterChanged && onFilterChanged(filterConditions); onFilterChanged && onFilterChanged(filterConditions);
}, },
}); });
@@ -107,9 +127,11 @@ const ItemsActionsBar = ({
<Button <Button
className={classNames(Classes.MINIMAL, 'button--filter')} className={classNames(Classes.MINIMAL, 'button--filter')}
text={ text={
filterCount <= 0 ? filterCount <= 0 ? (
(<T id={'filter'} />) : <T id={'filter'} />
(`${filterCount} ${formatMessage({ id: 'filters_applied' })}`) ) : (
`${filterCount} ${formatMessage({ id: 'filters_applied' })}`
)
} }
icon={<Icon icon="filter-16" iconSize={16} />} 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( export default compose(
withItemsActionsBar,
withDialogActions, withDialogActions,
withItems(({ itemsViews }) => ({ withItems(({ itemsViews }) => ({
itemsViews, itemsViews,
@@ -148,4 +177,5 @@ export default compose(
withResourceDetail(({ resourceFields }) => ({ withResourceDetail(({ resourceFields }) => ({
resourceFields, resourceFields,
})), })),
withItemsActions,
)(ItemsActionsBar); )(ItemsActionsBar);

View File

@@ -12,16 +12,18 @@ import {
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { If, Icon } from 'components';
import Icon from 'components/Icon';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import FilterDropdown from 'components/FilterDropdown'; import FilterDropdown from 'components/FilterDropdown';
import { If } from 'components';
import withResourceDetail from 'containers/Resources/withResourceDetails'; import withResourceDetail from 'containers/Resources/withResourceDetails';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withItemCategories from './withItemCategories';
import withItemCategoriesActions from './withItemCategoriesActions';
import { compose } from 'utils'; import { compose } from 'utils';
const ItemsCategoryActionsBar = ({ const ItemsCategoryActionsBar = ({
@@ -31,6 +33,12 @@ const ItemsCategoryActionsBar = ({
// #withDialog // #withDialog
openDialog, openDialog,
// #withItemCategories
categoriesViews,
// #withItemCategoriesActions
addItemCategoriesTableQueries,
// #ownProps // #ownProps
selectedRows = [], selectedRows = [],
onFilterChanged, onFilterChanged,
@@ -46,17 +54,21 @@ const ItemsCategoryActionsBar = ({
selectedRows, selectedRows,
]); ]);
// const handleDeleteCategory = useCallback((category) => { // const filterDropdown = FilterDropdown({
// onDeleteCategory(selectedRows); // fields: resourceFields,
// }, [selectedRows, onDeleteCategory]); // initialCondition: {
// fieldKey: 'name',
const filterDropdown = FilterDropdown({ // compatator: 'contains',
fields: resourceFields, // value: '',
onFilterChange: (filterConditions) => { // },
setFilterCount(filterConditions.length || 0); // onFilterChange: (filterConditions) => {
onFilterChanged && onFilterChanged(filterConditions); // setFilterCount(filterConditions.length || 0);
}, // addItemCategoriesTableQueries({
}); // filter_roles: filterConditions || '',
// });
// onFilterChanged && onFilterChanged(filterConditions);
// },
// });
const handelBulkDelete = useCallback(() => { const handelBulkDelete = useCallback(() => {
onBulkDelete && onBulkDelete(selectedRows.map((r) => r.id)); onBulkDelete && onBulkDelete(selectedRows.map((r) => r.id));
@@ -75,22 +87,29 @@ const ItemsCategoryActionsBar = ({
<Popover <Popover
minimal={true} minimal={true}
content={filterDropdown} // content={filterDropdown}
interactionKind={PopoverInteractionKind.CLICK} interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT} position={Position.BOTTOM_LEFT}
canOutsideClickClose={true}
> >
<Button <Button
className={classNames(Classes.MINIMAL, 'button--filter')} className={classNames(Classes.MINIMAL, 'button--filter')}
text={ filterCount <= 0 ? <T id={'filter'}/> : `${filterCount} filters applied`} text={
icon={<Icon icon='filter-16' iconSize={16} />} filterCount <= 0 ? (
<T id={'filter'} />
) : (
`${filterCount} filters applied`
)
}
icon={<Icon icon="filter-16" iconSize={16} />}
/> />
</Popover> </Popover>
<If condition={hasSelectedRows}> <If condition={hasSelectedRows}>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='trash-16' iconSize={16} />} icon={<Icon icon="trash-16" iconSize={16} />}
text={<T id={'delete'}/>} text={<T id={'delete'} />}
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={handelBulkDelete} onClick={handelBulkDelete}
/> />
@@ -98,13 +117,13 @@ const ItemsCategoryActionsBar = ({
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='file-import-16' iconSize={16} />} icon={<Icon icon="file-import-16" iconSize={16} />}
text={<T id={'import'}/>} text={<T id={'import'} />}
/> />
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='file-export-16' iconSize={16} />} icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'}/>} text={<T id={'export'} />}
/> />
</NavbarGroup> </NavbarGroup>
</DashboardActionsBar> </DashboardActionsBar>
@@ -114,7 +133,6 @@ const ItemsCategoryActionsBar = ({
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => ({
resourceName: 'items_categories', resourceName: 'items_categories',
}); });
const withItemsCategoriesActionsBar = connect(mapStateToProps); const withItemsCategoriesActionsBar = connect(mapStateToProps);
export default compose( export default compose(
@@ -124,4 +142,8 @@ export default compose(
withResourceDetail(({ resourceFields }) => ({ withResourceDetail(({ resourceFields }) => ({
resourceFields, resourceFields,
})), })),
// withItemCategories(({ categoriesViews }) => ({
// categoriesViews,
// })),
withItemCategoriesActions,
)(ItemsCategoryActionsBar); )(ItemsCategoryActionsBar);

View File

@@ -1,24 +1,21 @@
import React, { useEffect, useCallback, useState,useMemo } from 'react'; import React, { useEffect, useCallback, useState, useMemo } from 'react';
import { import { Route, Switch, useHistory } from 'react-router-dom';
Route, import { Intent, Alert } from '@blueprintjs/core';
Switch,
useHistory
} from 'react-router-dom';
import {
Intent,
Alert,
} from '@blueprintjs/core';
import { useQuery } from 'react-query'; 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 DashboardInsider from 'components/Dashboard/DashboardInsider';
import ItemsActionsBar from 'containers/Items/ItemsActionsBar';
import { compose } from 'utils'; import { compose } from 'utils';
import ItemsDataTable from './ItemsDataTable';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import ItemsViewsTabs from 'containers/Items/ItemsViewsTabs'; 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 AppToaster from 'components/AppToaster';
import withItems from 'containers/Items/withItems'; import withItems from 'containers/Items/withItems';
@@ -27,7 +24,6 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withItemsActions from 'containers/Items/withItemsActions'; import withItemsActions from 'containers/Items/withItemsActions';
import withViewsActions from 'containers/Views/withViewsActions'; import withViewsActions from 'containers/Views/withViewsActions';
function ItemsList({ function ItemsList({
// #withDashboardActions // #withDashboardActions
changePageTitle, changePageTitle,
@@ -51,37 +47,41 @@ function ItemsList({
const [bulkDelete, setBulkDelete] = useState(false); const [bulkDelete, setBulkDelete] = useState(false);
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const history = useHistory(); const history = useHistory();
useEffect(() => { useEffect(() => {
changePageTitle(formatMessage({id:'items_list'})); changePageTitle(formatMessage({ id: 'items_list' }));
}, [changePageTitle]); }, [changePageTitle]);
const fetchHook = useQuery('items-resource', () => { const fetchResourceViews = useQuery(
return Promise.all([ ['resource-views', 'items'],
requestFetchResourceViews('items'), (key, resourceName) => requestFetchResourceViews(resourceName),
requestFetchResourceFields('items'), );
]);
});
const fetchItems = useQuery(['items-table', itemsTableQuery], const fetchResourceFields = useQuery(
() => requestFetchItems({})); ['resource-fields', 'items'],
(key, resourceName) => requestFetchResourceFields(resourceName),
);
const fetchItems = useQuery(['items-table', itemsTableQuery], () =>
requestFetchItems({}),
);
// Handle click delete item. // Handle click delete item.
const handleDeleteItem = useCallback((item) => { const handleDeleteItem = useCallback(
setDeleteItem(item); (item) => {
}, [setDeleteItem]); setDeleteItem(item);
},
[setDeleteItem],
);
const handleEditItem = useCallback( const handleEditItem = useCallback(
(item) => { (item) => {
history.push(`/items/${item.id}/edit`); history.push(`/items/${item.id}/edit`);
}, },
[history] [history],
); );
// Handle cancel delete the item. // Handle cancel delete the item.
const handleCancelDeleteItem = useCallback(() => { const handleCancelDeleteItem = useCallback(() => {
setDeleteItem(false); setDeleteItem(false);
@@ -98,29 +98,43 @@ function ItemsList({
}); });
setDeleteItem(false); setDeleteItem(false);
}); });
}, [requestDeleteItem, deleteItem,formatMessage]); }, [requestDeleteItem, deleteItem, formatMessage]);
// Handle fetch data table. const handleFetchData = useCallback(
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => { ({ pageIndex, pageSize, sortBy }) => {
addItemsTableQueries({ const page = pageIndex + 1;
...(sortBy.length > 0) ? {
column_sort_order: sortBy[0].id, addItemsTableQueries({
sort_order: sortBy[0].desc ? 'desc' : 'asc', ...(sortBy.length > 0
} : {}, ? {
}); column_sort_by: sortBy[0].id,
}, [fetchItems, addItemsTableQueries]); sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
page_size: pageSize,
page,
});
},
[addItemsTableQueries],
);
// Handle filter change to re-fetch the items. // Handle filter change to re-fetch the items.
const handleFilterChanged = useCallback((filterConditions) => { const handleFilterChanged = useCallback(
addItemsTableQueries({ (filterConditions) => {
filter_roles: filterConditions || '', addItemsTableQueries({
}); filter_roles: filterConditions || '',
}, [fetchItems]); });
},
[fetchItems],
);
// Handle custom view change to re-fetch the items. // Handle custom view change to re-fetch the items.
const handleCustomViewChanged = useCallback((customViewId) => { const handleCustomViewChanged = useCallback(
setTableLoading(true); (customViewId) => {
}, [fetchItems]); setTableLoading(true);
},
[fetchItems],
);
useEffect(() => { useEffect(() => {
if (tableLoading && !fetchItems.isFetching) { if (tableLoading && !fetchItems.isFetching) {
@@ -129,108 +143,124 @@ function ItemsList({
}, [tableLoading, fetchItems.isFetching]); }, [tableLoading, fetchItems.isFetching]);
// Handle selected rows change. // Handle selected rows change.
const handleSelectedRowsChange = useCallback((accounts) => { const handleSelectedRowsChange = useCallback(
setSelectedRows(accounts); (accounts) => {
}, [setSelectedRows]); setSelectedRows(accounts);
},
// Calculates the data table selected rows count. [setSelectedRows],
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [selectedRows]); );
// Calculates the data table selected rows count.
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
selectedRows,
]);
// Handle items bulk delete button click., // Handle items bulk delete button click.,
const handleBulkDelete = useCallback((itemsIds) => { const handleBulkDelete = useCallback(
setBulkDelete(itemsIds); (itemsIds) => {
}, [setBulkDelete]); setBulkDelete(itemsIds);
},
[setBulkDelete],
);
// Handle confirm items bulk delete. // Handle confirm items bulk delete.
const handleConfirmBulkDelete = useCallback(() => { const handleConfirmBulkDelete = useCallback(() => {
requestDeleteBulkItems(bulkDelete).then(() => { requestDeleteBulkItems(bulkDelete)
setBulkDelete(false); .then(() => {
AppToaster.show({ setBulkDelete(false);
message: formatMessage({ id: 'the_items_has_been_successfully_deleted' }), AppToaster.show({
intent: Intent.SUCCESS, message: formatMessage({
id: 'the_items_has_been_successfully_deleted',
}),
intent: Intent.SUCCESS,
});
})
.catch((errors) => {
setBulkDelete(false);
}); });
}).catch((errors) => { }, [requestDeleteBulkItems, bulkDelete, formatMessage]);
setBulkDelete(false);
});
}, [requestDeleteBulkItems, bulkDelete,formatMessage]);
// Handle cancel accounts bulk delete.
const handleCancelBulkDelete = useCallback(() => {
setBulkDelete(false);
}, []);
// Handle cancel accounts bulk delete.
const handleCancelBulkDelete = useCallback(() => {
setBulkDelete(false);
}, []);
return ( return (
<DashboardInsider <DashboardInsider
isLoading={fetchHook.isFetching} loading={fetchResourceViews.isFetching || fetchResourceFields.isFetching}
name={'items-list'}> // isLoading={fetchHook.isFetching}
name={'items-list'}
>
<ItemsActionsBar <ItemsActionsBar
selectedRows={selectedRows} selectedRows={selectedRows}
onFilterChanged={handleFilterChanged} onFilterChanged={handleFilterChanged}
onBulkDelete={handleBulkDelete}/> onBulkDelete={handleBulkDelete}
/>
<DashboardPageContent> <DashboardPageContent>
<Switch> <Switch>
<Route <Route
exact={true} exact={true}
path={[ path={['/items/:custom_view_id/custom_view', '/items']}
'/items/:custom_view_id/custom_view', >
'/items' <ItemsViewsTabs onViewChanged={handleCustomViewChanged} />
]}>
<ItemsViewsTabs
onViewChanged={handleCustomViewChanged} />
<ItemsDataTable <ItemsDataTable
loading={tableLoading} loading={fetchItems.isFetching}
onDeleteItem={handleDeleteItem} onDeleteItem={handleDeleteItem}
onEditItem={handleEditItem} onEditItem={handleEditItem}
onFetchData={handleFetchData} onFetchData={handleFetchData}
onSelectedRowsChange={handleSelectedRowsChange} /> onSelectedRowsChange={handleSelectedRowsChange}
/>
<Alert <Alert
cancelButtonText={<T id={'cancel'}/>} cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'}/>} confirmButtonText={<T id={'delete'} />}
icon="trash" icon="trash"
intent={Intent.DANGER} intent={Intent.DANGER}
isOpen={deleteItem} isOpen={deleteItem}
onCancel={handleCancelDeleteItem} onCancel={handleCancelDeleteItem}
onConfirm={handleConfirmDeleteItem}> onConfirm={handleConfirmDeleteItem}
>
<p> <p>
<FormattedHTMLMessage <FormattedHTMLMessage
id={'once_delete_this_item_you_will_able_to_restore_it'} /> id={'once_delete_this_item_you_will_able_to_restore_it'}
/>
</p> </p>
</Alert> </Alert>
<Alert <Alert
cancelButtonText={<T id={'cancel'}/>} cancelButtonText={<T id={'cancel'} />}
confirmButtonText={`${formatMessage({id:'delete'})} (${selectedRowsCount})`} confirmButtonText={`${formatMessage({
icon="trash" id: 'delete',
intent={Intent.DANGER} })} (${selectedRowsCount})`}
isOpen={bulkDelete} icon="trash"
onCancel={handleCancelBulkDelete} intent={Intent.DANGER}
onConfirm={handleConfirmBulkDelete} isOpen={bulkDelete}
> onCancel={handleCancelBulkDelete}
<p> onConfirm={handleConfirmBulkDelete}
<T id={'once_delete_these_items_you_will_not_able_restore_them'} /> >
</p> <p>
</Alert> <T
id={'once_delete_these_items_you_will_not_able_restore_them'}
/>
</p>
</Alert>
</Route> </Route>
</Switch> </Switch>
</DashboardPageContent> </DashboardPageContent>
</DashboardInsider> </DashboardInsider>
) );
} }
export default compose( export default compose(
withItems(({ itemsTableQuery }) => ({
itemsTableQuery,
})),
withResourceActions, withResourceActions,
withDashboardActions, withDashboardActions,
withItemsActions, withItemsActions,
withViewsActions, withViewsActions,
withItems(({ itemsTableQuery }) => ({
itemsTableQuery,
})),
)(ItemsList); )(ItemsList);

View File

@@ -1,4 +1,4 @@
import React, {useEffect} from 'react'; import React, { useEffect, useRef } from 'react';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
@@ -7,20 +7,22 @@ import {
NavbarGroup, NavbarGroup,
Tabs, Tabs,
Tab, Tab,
Button Button,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import { Link, withRouter } from 'react-router-dom'; import { Link, withRouter } from 'react-router-dom';
import { compose } from 'utils'; 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 withItemsActions from 'containers/Items/withItemsActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withViewDetail from 'containers/Views/withViewDetails'; import withViewDetail from 'containers/Views/withViewDetails';
import withItems from 'containers/Items/withItems'; import withItems from 'containers/Items/withItems';
import { FormattedMessage as T} from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
function ItemsViewsTabs({ function ItemsViewsTabs({
// #withViewDetail // #withViewDetail
@@ -42,7 +44,7 @@ function ItemsViewsTabs({
onViewChanged, onViewChanged,
}) { }) {
const history = useHistory(); const history = useHistory();
const { custom_view_id: customViewId } = useParams(); const { custom_view_id: customViewId = null } = useParams();
const handleClickNewView = () => { const handleClickNewView = () => {
setTopbarEditView(null); setTopbarEditView(null);
@@ -51,12 +53,12 @@ function ItemsViewsTabs({
const handleViewLinkClick = () => { const handleViewLinkClick = () => {
setTopbarEditView(customViewId); setTopbarEditView(customViewId);
} };
useEffect(() => { useEffect(() => {
changeItemsCurrentView(customViewId || -1); changeItemsCurrentView(customViewId || -1);
setTopbarEditView(customViewId); setTopbarEditView(customViewId);
changePageSubtitle((customViewId && viewItem) ? viewItem.name : ''); changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
addItemsTableQueries({ addItemsTableQueries({
custom_view_id: customViewId || null, custom_view_id: customViewId || null,
@@ -67,45 +69,37 @@ function ItemsViewsTabs({
changeItemsCurrentView(-1); changeItemsCurrentView(-1);
changePageSubtitle(''); changePageSubtitle('');
}; };
}, [customViewId]); }, [customViewId, addItemsTableQueries, changeItemsCurrentView]);
useUpdateEffect(() => { useUpdateEffect(() => {
onViewChanged && onViewChanged(customViewId); onViewChanged && onViewChanged(customViewId);
}, [customViewId]); }, [customViewId]);
const tabs = itemsViews.map(view => { const debounceChangeHistory = useRef(
const baseUrl = '/items'; debounce((toUrl) => {
const link = ( history.push(toUrl);
<Link to={`${baseUrl}/${view.id}/custom_view`} onClick={handleViewLinkClick}> }, 250),
{view.name} );
</Link>
);
return (<Tab id={`custom_view_${view.id}`} title={link} />);
});
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 ( return (
<Navbar className='navbar--dashboard-views'> <Navbar className="navbar--dashboard-views">
<NavbarGroup align={Alignment.LEFT}> <NavbarGroup align={Alignment.LEFT}>
<Tabs <DashboardViewsTabs
id='navbar' initialViewId={customViewId}
large={true} baseUrl={'/items'}
selectedTabId={customViewId ? `custom_view_${customViewId}` : 'all'} tabs={tabs}
className='tabs--dashboard-views' onNewViewTabClick={handleClickNewView}
> onChange={handleTabsChange}
<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>
</NavbarGroup> </NavbarGroup>
</Navbar> </Navbar>
); );
@@ -123,8 +117,8 @@ export default compose(
withItemsViewsTabs, withItemsViewsTabs,
withDashboardActions, withDashboardActions,
withItemsActions, withItemsActions,
withViewDetail, withViewDetail(),
withItems( ({ itemsViews }) => ({ withItems(({ itemsViews }) => ({
itemsViews, itemsViews,
})) })),
)(ItemsViewsTabs); )(ItemsViewsTabs);

View File

@@ -1,8 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { getItemsCategoriesListFactory } from 'store/itemCategories/ItemsCategories.selectors';
getItemsCategoriesListFactory import { getResourceViews } from 'store/customViews/customViews.selectors';
} from 'store/itemCategories/ItemsCategories.selectors';
export default (mapState) => { export default (mapState) => {
const getItemsCategoriesList = getItemsCategoriesListFactory(); const getItemsCategoriesList = getItemsCategoriesListFactory();
@@ -10,10 +8,11 @@ export default (mapState) => {
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
const mapped = { const mapped = {
categoriesList: getItemsCategoriesList(state, props), categoriesList: getItemsCategoriesList(state, props),
itemCategoriesViews: getResourceViews(state, props, 'items_categories'),
categoriesTableLoading: state.itemCategories.loading, categoriesTableLoading: state.itemCategories.loading,
}; };
return mapState ? mapState(mapped, state, props) : mapState; return mapState ? mapState(mapped, state, props) : mapState;
}; };
return connect(mapStateToProps); return connect(mapStateToProps);
}; };

View File

@@ -4,16 +4,30 @@ import {
submitItemCategory, submitItemCategory,
deleteItemCategory, deleteItemCategory,
editItemCategory, editItemCategory,
deleteBulkItemCategories deleteBulkItemCategories,
} from 'store/itemCategories/itemsCategory.actions'; } from 'store/itemCategories/itemsCategory.actions';
import t from 'store/types';
export const mapDispatchToProps = (dispatch) => ({ export const mapDispatchToProps = (dispatch) => ({
requestSubmitItemCategory: (form) => dispatch(submitItemCategory({ form })), requestSubmitItemCategory: (form) => dispatch(submitItemCategory({ form })),
requestFetchItemCategories: (query) => dispatch(fetchItemCategories({ query })), requestFetchItemCategories: (query) =>
dispatch(fetchItemCategories({ query })),
requestDeleteItemCategory: (id) => dispatch(deleteItemCategory(id)), requestDeleteItemCategory: (id) => dispatch(deleteItemCategory(id)),
requestEditItemCategory: (id, form) => dispatch(editItemCategory(id, form)), 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);

View File

@@ -50,9 +50,11 @@ function CurrenciesList({
const fetchCurrencies = useQuery('currencies-table', const fetchCurrencies = useQuery('currencies-table',
() => requestFetchCurrencies(), () => requestFetchCurrencies(),
{ manual: true }, { enabled: true },
); );
useEffect(() => { useEffect(() => {
changePreferencesPageTitle(formatMessage({ id: 'currencies' })); changePreferencesPageTitle(formatMessage({ id: 'currencies' }));
}, [changePreferencesPageTitle, formatMessage]); }, [changePreferencesPageTitle, formatMessage]);
@@ -147,7 +149,7 @@ function CurrenciesList({
); );
const handleDataTableFetchData = useCallback(() => { const handleDataTableFetchData = useCallback(() => {
fetchCurrencies.refetch(); // fetchCurrencies.refetch();
}, [fetchCurrencies]); }, [fetchCurrencies]);
return ( return (

View File

@@ -202,12 +202,11 @@ function BillForm({
const form = { const form = {
...values, ...values,
entries: values.entries.filter( entries: values.entries.filter((item) => item.item_id && item.quantity),
(item) => item.item_id && item.quantity,
),
}; };
const requestForm = { ...form };
if (bill && bill.id) { if (bill && bill.id) {
requestEditBill(bill.id, form) requestEditBill(bill.id, requestForm)
.then((response) => { .then((response) => {
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage({
@@ -223,7 +222,7 @@ function BillForm({
setSubmitting(false); setSubmitting(false);
}); });
} else { } else {
requestSubmitBill(form) requestSubmitBill(requestForm)
.then((response) => { .then((response) => {
AppToaster.show({ AppToaster.show({
message: formatMessage( message: formatMessage(
@@ -295,7 +294,10 @@ function BillForm({
<Row> <Row>
<Col> <Col>
<FormGroup label={<T id={'note'} />} className={'form-group--'}> <FormGroup label={<T id={'note'} />} className={'form-group--'}>
<TextArea growVertically={true} {...formik.getFieldProps('note')} /> <TextArea
growVertically={true}
{...formik.getFieldProps('note')}
/>
</FormGroup> </FormGroup>
</Col> </Col>

View File

@@ -106,6 +106,7 @@ function BillsDataTable({
text={formatMessage({ id: 'delete_bill' })} text={formatMessage({ id: 'delete_bill' })}
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={handleDeleteBill(bill)} onClick={handleDeleteBill(bill)}
icon={<Icon icon="trash-16" iconSize={16} />}
/> />
</Menu> </Menu>
), ),

View File

@@ -1,309 +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 { omit } from 'lodash';
import { Row, Col } from 'react-grid-system';
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}
/>
<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>
<BillFormFooter
formik={formik}
onSubmit={handleSubmitClick}
onCancelClick={handleCancelClick}
/>
</form>
</div>
);
}
export default compose(
withBillActions,
withDashboardActions,
withMediaActions,
)(BillForm);

View File

@@ -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);

View File

@@ -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);

View 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);

View File

@@ -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);

View File

@@ -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);

View 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);

View File

@@ -2,14 +2,22 @@ import React from 'react';
import { Intent, Button } from '@blueprintjs/core'; import { Intent, Button } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
export default function BillFormFooter({ export default function PaymentMadeFormFooter({
formik: { isSubmitting }, formik: { isSubmitting, resetForm },
onSubmitClick, onSubmitClick,
onCancelClick, onCancelClick,
paymentMade,
}) { }) {
return ( return (
<div> <div className={'estimate-form__floating-footer'}>
<Button disabled={isSubmitting} intent={Intent.PRIMARY} type="submit"> <Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
type="submit"
onClick={() => {
onSubmitClick({ redirect: true });
}}
>
<T id={'save_send'} /> <T id={'save_send'} />
</Button> </Button>
@@ -19,11 +27,14 @@ export default function BillFormFooter({
className={'ml1'} className={'ml1'}
name={'save'} name={'save'}
type="submit" type="submit"
onClick={() => {
onSubmitClick({ redirect: false });
}}
> >
<T id={'save'} /> <T id={'save'} />
</Button> </Button>
<Button className={'ml1'} disabled={isSubmitting}> <Button className={'ml1'} disabled={isSubmitting} s>
<T id={'clear'} /> <T id={'clear'} />
</Button> </Button>

View File

@@ -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);

View File

@@ -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(({}) => ({}))

View 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);

View File

@@ -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);

View File

@@ -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);
};

View File

@@ -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);

View File

@@ -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);
};

View File

@@ -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);

View File

@@ -173,7 +173,7 @@ const EstimateForm = ({
index: index + 1, index: index + 1,
})); }));
}; };
// debugger;
const initialValues = useMemo( const initialValues = useMemo(
() => ({ () => ({
...(estimate ...(estimate
@@ -197,14 +197,6 @@ const EstimateForm = ({
[estimate, defaultInitialValues, defaultEstimate], [estimate, defaultInitialValues, defaultEstimate],
); );
// const initialValues = useMemo(
// () => ({
// ...defaultInitialValues,
// entries: orderingProductsIndex(defaultInitialValues.entries),
// }),
// [defaultEstimate, defaultInitialValues, estimate],
// );
const initialAttachmentFiles = useMemo(() => { const initialAttachmentFiles = useMemo(() => {
return estimate && estimate.media return estimate && estimate.media
? estimate.media.map((attach) => ({ ? estimate.media.map((attach) => ({
@@ -263,7 +255,7 @@ const EstimateForm = ({
} }
}, },
}); });
console.log(formik.errors ,'ERROR');
const handleSubmitClick = useCallback( const handleSubmitClick = useCallback(
(payload) => { (payload) => {
setPayload(payload); setPayload(payload);
@@ -315,7 +307,6 @@ const EstimateForm = ({
onClickAddNewRow={handleClickAddNewRow} onClickAddNewRow={handleClickAddNewRow}
onClickClearAllLines={handleClearAllLines} onClickClearAllLines={handleClearAllLines}
formik={formik} formik={formik}
// defaultRow={defaultEstimate}
/> />
<Row> <Row>
@@ -324,7 +315,10 @@ const EstimateForm = ({
label={<T id={'customer_note'} />} label={<T id={'customer_note'} />}
className={'form-group--customer_note'} className={'form-group--customer_note'}
> >
<TextArea growVertically={true} {...formik.getFieldProps('note')} /> <TextArea
growVertically={true}
{...formik.getFieldProps('note')}
/>
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={<T id={'terms_conditions'} />} label={<T id={'terms_conditions'} />}

View File

@@ -97,12 +97,9 @@ function EstimateFormHeader({
labelProp={'display_name'} labelProp={'display_name'}
/> />
</FormGroup> </FormGroup>
<Row>
<Col
// md={9} push={{ md: 3 }} <Row>
> <Col>
<FormGroup <FormGroup
label={<T id={'estimate_date'} />} label={<T id={'estimate_date'} />}
inline={true} inline={true}
@@ -110,7 +107,7 @@ function EstimateFormHeader({
className={classNames( className={classNames(
'form-group--select-list', 'form-group--select-list',
Classes.FILL, Classes.FILL,
'form-group--estimate-date' 'form-group--estimate-date',
)} )}
intent={ intent={
errors.estimate_date && touched.estimate_date && Intent.DANGER errors.estimate_date && touched.estimate_date && Intent.DANGER
@@ -127,17 +124,14 @@ function EstimateFormHeader({
/> />
</FormGroup> </FormGroup>
</Col> </Col>
<Col <Col>
// md={3} pull={{ md: 9 }}
>
<FormGroup <FormGroup
label={<T id={'expiration_date'} />} label={<T id={'expiration_date'} />}
inline={true} inline={true}
className={classNames( className={classNames(
'form-group--select-list', 'form-group--select-list',
'form-group--expiration-date', 'form-group--expiration-date',
Classes.FILL Classes.FILL,
)} )}
intent={ intent={
errors.expiration_date && errors.expiration_date &&
@@ -180,7 +174,7 @@ function EstimateFormHeader({
{...getFieldProps('estimate_number')} {...getFieldProps('estimate_number')}
/> />
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={<T id={'reference'} />} label={<T id={'reference'} />}
inline={true} inline={true}

View File

@@ -17,10 +17,10 @@ export default (mapState) => {
const mapped = { const mapped = {
estimatesCurrentPage: getEstimatesItems(state, props, query), estimatesCurrentPage: getEstimatesItems(state, props, query),
estimateViews: getResourceViews(state, props, 'sales_estimates'), estimateViews: getResourceViews(state, props, 'sales_estimates'),
estimateItems: state.sales_estimates.items, estimateItems: state.salesEstimates.items,
estimateTableQuery: query, estimateTableQuery: query,
estimatesPageination: getEstimatesPaginationMeta(state, props, query), estimatesPageination: getEstimatesPaginationMeta(state, props, query),
estimatesLoading: state.sales_estimates.loading, estimatesLoading: state.salesEstimates.loading,
}; };
return mapState ? mapState(mapped, state, props) : mapped; return mapState ? mapState(mapped, state, props) : mapped;
}; };

View File

@@ -163,7 +163,7 @@ function InvoiceForm({
index: index + 1, index: index + 1,
})); }));
}; };
// debugger;
const initialValues = useMemo( const initialValues = useMemo(
() => ({ () => ({
...(invoice ...(invoice
@@ -187,14 +187,6 @@ function InvoiceForm({
[invoice, defaultInitialValues, defaultInvoice], [invoice, defaultInitialValues, defaultInvoice],
); );
// const initialValues = useMemo(
// () => ({
// ...defaultInitialValues,
// entries: orderingIndex(defaultInitialValues.entries),
// }),
// [defaultInvoice, defaultInitialValues, invoice],
// );
const initialAttachmentFiles = useMemo(() => { const initialAttachmentFiles = useMemo(() => {
return invoice && invoice.media return invoice && invoice.media
? invoice.media.map((attach) => ({ ? invoice.media.map((attach) => ({
@@ -337,7 +329,8 @@ function InvoiceForm({
onDrop={handleDropFiles} onDrop={handleDropFiles}
onDeleteFile={handleDeleteFile} onDeleteFile={handleDeleteFile}
hint={'Attachments: Maxiumum size: 20MB'} hint={'Attachments: Maxiumum size: 20MB'}
/></Col> />
</Col>
</Row> </Row>
</form> </form>

View File

@@ -19,6 +19,12 @@ function Invoices({
const history = useHistory(); const history = useHistory();
const { id } = useParams(); const { id } = useParams();
const fetchInvoice = useQuery(
['invoice', id],
(key, _id) => requsetFetchInvoice(_id),
{ enabled: !!id },
);
// Handle fetch Items data table or list // Handle fetch Items data table or list
const fetchItems = useQuery('items-table', () => requestFetchItems({})); const fetchItems = useQuery('items-table', () => requestFetchItems({}));
@@ -33,12 +39,6 @@ function Invoices({
requestFetchCustomers({}), requestFetchCustomers({}),
); );
const fetchInvoice = useQuery(
['invoice', id],
(key, _id) => requsetFetchInvoice(_id),
{ enabled: !!id },
);
const handleCancel = useCallback(() => { const handleCancel = useCallback(() => {
history.goBack(); history.goBack();
}, [history]); }, [history]);

View File

@@ -5,17 +5,18 @@ import {
deleteInvoice, deleteInvoice,
fetchInvoice, fetchInvoice,
fetchInvoicesTable, fetchInvoicesTable,
dueInvoices,
} from 'store/Invoice/invoices.actions'; } from 'store/Invoice/invoices.actions';
import t from 'store/types'; import t from 'store/types';
const mapDipatchToProps = (dispatch) => ({ const mapDipatchToProps = (dispatch) => ({
requestSubmitInvoice: (form) => dispatch(submitInvoice({ form })), requestSubmitInvoice: (form) => dispatch(submitInvoice({ form })),
requsetFetchInvoice: (id) => dispatch(fetchInvoice({ id })), requsetFetchInvoice: (id) => dispatch(fetchInvoice({ id })),
requestEditInvoice: (id, form) => dispatch(editInvoice( id, form )), requestEditInvoice: (id, form) => dispatch(editInvoice(id, form)),
requestFetchInvoiceTable: (query = {}) => requestFetchInvoiceTable: (query = {}) =>
dispatch(fetchInvoicesTable({ query: { ...query } })), dispatch(fetchInvoicesTable({ query: { ...query } })),
requestDeleteInvoice: (id) => dispatch(deleteInvoice({ id })), requestDeleteInvoice: (id) => dispatch(deleteInvoice({ id })),
requestFetchDueInvoices: (id) => dispatch(dueInvoices({ id })),
changeInvoiceView: (id) => changeInvoiceView: (id) =>
dispatch({ dispatch({
type: t.INVOICES_SET_CURRENT_VIEW, type: t.INVOICES_SET_CURRENT_VIEW,

View File

@@ -4,10 +4,13 @@ import {
getInvoiceCurrentPageFactory, getInvoiceCurrentPageFactory,
getInvoicePaginationMetaFactory, getInvoicePaginationMetaFactory,
getInvoiceTableQueryFactory, getInvoiceTableQueryFactory,
getInvoiceTableQuery,
getdueInvoices,
} from 'store/Invoice/invoices.selector'; } from 'store/Invoice/invoices.selector';
export default (mapState) => { export default (mapState) => {
const getInvoicesItems = getInvoiceCurrentPageFactory(); const getInvoicesItems = getInvoiceCurrentPageFactory();
const getInvoicesPaginationMeta = getInvoicePaginationMetaFactory(); const getInvoicesPaginationMeta = getInvoicePaginationMetaFactory();
const getInvoiceTableQuery = getInvoiceTableQueryFactory(); const getInvoiceTableQuery = getInvoiceTableQueryFactory();
@@ -17,10 +20,11 @@ export default (mapState) => {
const mapped = { const mapped = {
invoicesCurrentPage: getInvoicesItems(state, props, query), invoicesCurrentPage: getInvoicesItems(state, props, query),
invoicesViews: getResourceViews(state, props, 'sales_invoices'), invoicesViews: getResourceViews(state, props, 'sales_invoices'),
invoicesItems: state.sales_invoices.items, invoicesItems: state.salesInvoices.items,
invoicesTableQuery: query, invoicesTableQuery: query,
invoicesPageination: getInvoicesPaginationMeta(state, props, 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; return mapState ? mapState(mapped, state, props) : mapped;
}; };

View File

@@ -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);

View File

@@ -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 * as Yup from 'yup';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import moment from 'moment'; import moment from 'moment';
import { Intent, FormGroup, TextArea } from '@blueprintjs/core'; import { Intent, FormGroup, TextArea } from '@blueprintjs/core';
import { useParams, useHistory } from 'react-router-dom';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import { pick, values } from 'lodash';
import PaymentReceiveHeader from './PaymentReceiveFormHeader'; import PaymentReceiveHeader from './PaymentReceiveFormHeader';
// PaymentReceiptItemsTable import PaymentReceiveItemsTable from './PaymentReceiveItemsTable';
import PaymentReceiveFooter from './PaymentReceiveFormFooter'; import PaymentReceiveFooter from './PaymentReceiveFormFooter';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withMediaActions from 'containers/Media/withMediaActions'; 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 { AppToaster } from 'components';
import Dragzone from 'components/Dragzone'; import Dragzone from 'components/Dragzone';
import useMedia from 'hooks/useMedia'; import useMedia from 'hooks/useMedia';
import { compose, repeatValue } from 'utils'; import { compose, repeatValue } from 'utils';
const MIN_LINES_NUMBER = 4; const MIN_LINES_NUMBER = 5;
function PaymentReceiveForm({ function PaymentReceiveForm({
//#withMedia //#withMedia
requestSubmitMedia, requestSubmitMedia,
requestDeleteMedia, requestDeleteMedia,
//#WithPaymentReceiveActions //#WithPaymentReceiveActions
requestSubmitPaymentReceive, requestSubmitPaymentReceive,
requestEditPaymentReceive,
//#withDashboard //#withDashboard
changePageTitle, changePageTitle,
changePageSubtitle, changePageSubtitle,
//#withPaymentReceiveDetail //#withPaymentReceiveDetail
paymentReceive,
paymentReceiveInvoices,
paymentReceivesItems,
//#OWn Props //#OWn Props
payment_receive, // payment_receive,
onFormSubmit, onFormSubmit,
onCancelForm, onCancelForm,
dueInvoiceLength,
onCustomerChange,
}) { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const [payload, setPayload] = useState({}); const [payload, setPayload] = useState({});
const { id } = useParams();
const { const {
setFiles, setFiles,
saveMedia, saveMedia,
@@ -63,43 +77,52 @@ function PaymentReceiveForm({
}; };
useEffect(() => { useEffect(() => {
if (payment_receive && payment_receive.id) { if (paymentReceive && paymentReceive.id) {
return;
} else {
onCustomerChange && onCustomerChange(formik.values.customer_id);
}
});
useEffect(() => {
if (paymentReceive && paymentReceive.id) {
changePageTitle(formatMessage({ id: 'edit_payment_receive' })); changePageTitle(formatMessage({ id: 'edit_payment_receive' }));
} else { } else {
changePageTitle(formatMessage({ id: 'new_payment_receive' })); changePageTitle(formatMessage({ id: 'payment_receive' }));
} }
}, [changePageTitle, payment_receive, formatMessage]); }, [changePageTitle, paymentReceive, formatMessage]);
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
customer_id: Yup.string() customer_id: Yup.string()
.label(formatMessage({ id: 'customer_name_' })) .label(formatMessage({ id: 'customer_name_' }))
.required(), .required(),
deposit_account_id: Yup.number()
.required()
.label(formatMessage({ id: 'deposit_account_' })),
payment_date: Yup.date() payment_date: Yup.date()
.required() .required()
.label(formatMessage({ id: 'payment_date_' })), .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() payment_receive_no: Yup.number()
.required() .required()
.label(formatMessage({ id: 'payment_receive_no_' })), .label(formatMessage({ id: 'payment_receive_no_' })),
reference_no: Yup.string().min(1).max(255), reference_no: Yup.string().min(1).max(255).nullable(),
statement: Yup.string() description: Yup.string().nullable(),
.trim()
.min(1)
.max(1024)
.label(formatMessage({ id: 'statement' })),
entries: Yup.array().of( entries: Yup.array().of(
Yup.object().shape({ Yup.object().shape({
payment_amount: Yup.number().nullable(), 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() .nullable()
.when(['payment_amount'], { .when(['payment_amount'], {
is: (payment_amount) => payment_amount, is: (payment_amount) => payment_amount,
then: Yup.number().required(), then: Yup.number().required(),
}), }),
description: Yup.string().nullable(),
}), }),
), ),
}); });
@@ -114,9 +137,12 @@ function PaymentReceiveForm({
const defaultPaymentReceive = useMemo( const defaultPaymentReceive = useMemo(
() => ({ () => ({
item_id: null, invoice_id: '',
payment_amount: null, invoice_date: moment(new Date()).format('YYYY-MM-DD'),
description: null, invoice_no: '',
balance: '',
due_amount: '',
payment_amount: '',
}), }),
[], [],
); );
@@ -126,29 +152,53 @@ function PaymentReceiveForm({
deposit_account_id: '', deposit_account_id: '',
payment_date: moment(new Date()).format('YYYY-MM-DD'), payment_date: moment(new Date()).format('YYYY-MM-DD'),
reference_no: '', reference_no: '',
statement: '', payment_receive_no: '',
// receive_amount: '',
description: '',
entries: [...repeatValue(defaultPaymentReceive, MIN_LINES_NUMBER)], entries: [...repeatValue(defaultPaymentReceive, MIN_LINES_NUMBER)],
}), }),
[defaultPaymentReceive], [defaultPaymentReceive],
); );
const orderingIndex = (_entries) => {
return _entries.map((item, index) => ({
...item,
index: index + 1,
}));
};
const initialValues = useMemo( const initialValues = useMemo(
() => ({ () => ({
...defaultInitialValues, ...(paymentReceive
entries: defaultInitialValues.entries, ? {
...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(() => { const initialAttachmentFiles = useMemo(() => {
return payment_receive && payment_receive.media return paymentReceive && paymentReceive.media
? payment_receive.media.map((attach) => ({ ? paymentReceive.media.map((attach) => ({
preview: attach.attachment_file, preview: attach.attachment_file,
uploaded: true, uploaded: true,
metadata: { ...attach }, metadata: { ...attach },
})) }))
: []; : [];
}, [payment_receive]); }, [paymentReceive]);
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
@@ -157,38 +207,52 @@ function PaymentReceiveForm({
...initialValues, ...initialValues,
}, },
onSubmit: async (values, { setSubmitting, setErrors, resetForm }) => { onSubmit: async (values, { setSubmitting, setErrors, resetForm }) => {
setSubmitting(true);
const entries = formik.values.entries.filter((item) => {
if (item.invoice_id !== undefined) {
return { ...item };
}
});
const form = { const form = {
...values, ...values,
entries,
}; };
const savePaymentReceive = (mediaIds) =>
new Promise((resolve, reject) => {
const requestForm = { ...form, media_ids: mediaIds };
requestSubmitPaymentReceive(requestForm) const requestForm = { ...form };
.the((response) => {
AppToaster.show({ if (paymentReceive && paymentReceive.id) {
message: formatMessage({ requestEditPaymentReceive(paymentReceive.id, requestForm)
id: 'the_payment_receive_has_been_successfully_created', .then((response) => {
}), AppToaster.show({
intent: Intent.SUCCESS, message: formatMessage({
}); id: 'the_payment_receive_has_been_successfully_edited',
setSubmitting(false); }),
clearSavedMediaIds(); intent: Intent.SUCCESS,
savePaymentReceiveSubmit({ action: 'new', ...payload });
})
.catch((errors) => {
setSubmitting(false);
}); });
}); setSubmitting(false);
Promise.all([saveMedia(), deleteMedia()]) savePaymentReceiveSubmit({ action: 'update', ...payload });
.then(([savedMediaResponses]) => { resetForm();
const mediaIds = savedMediaResponses.map((res) => res.data.media.id); })
savedMediaIds.current = mediaIds; .catch((error) => {
return savedMediaResponses; setSubmitting(false);
}) });
.then(() => { } else {
return savePaymentReceive(savedMediaIds.current); 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 +286,47 @@ function PaymentReceiveForm({
formik.resetForm(); formik.resetForm();
}; };
const handleClickAddNewRow = () => {
formik.setFieldValue(
'entries',
orderingIndex([...formik.values.entries, defaultPaymentReceive]),
);
};
const handleClearAllLines = () => {
formik.setFieldValue(
'entries',
orderingIndex([...repeatValue(defaultPaymentReceive, MIN_LINES_NUMBER)]),
);
};
return ( return (
<div className={'payment_receive_form'}> <div className={'payment_receive_form'}>
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
<PaymentReceiveHeader formik={formik} /> <PaymentReceiveHeader formik={formik} />
<FormGroup <PaymentReceiveItemsTable
label={<T id={'statement'} />} entries={formik.values.entries}
className={'form-group--statement'} customer_id={formik.values.customer_id}
> onClickAddNewRow={handleClickAddNewRow}
<TextArea onClickClearAllLines={handleClearAllLines}
growVertically={true} formik={formik}
{...formik.getFieldProps('statement')} invoices={paymentReceiveInvoices}
/> />
</FormGroup> {/* <Dragzone
<Dragzone
initialFiles={initialAttachmentFiles} initialFiles={initialAttachmentFiles}
onDrop={handleDropFiles} onDrop={handleDropFiles}
onDeleteFile={handleDeleteFile} onDeleteFile={handleDeleteFile}
hint={'Attachments: Maxiumum size: 20MB'} hint={'Attachments: Maxiumum size: 20MB'}
/> /> */}
<PaymentReceiveFooter
formik={formik}
onSubmit={handleSubmitClick}
onCancel={handleCancelClick}
onClearClick={handleClearClick}
/>
</form> </form>
<PaymentReceiveFooter
formik={formik}
onSubmitClick={handleSubmitClick}
paymentReceive={paymentReceive}
onCancel={handleCancelClick}
onClearClick={handleClearClick}
/>
</div> </div>
); );
} }
@@ -257,4 +335,8 @@ export default compose(
withPaymentReceivesActions, withPaymentReceivesActions,
withDashboardActions, withDashboardActions,
withMediaActions, withMediaActions,
withPaymentReceives(({ paymentReceivesItems }) => ({
paymentReceivesItems,
})),
withPaymentReceiveDetail(),
)(PaymentReceiveForm); )(PaymentReceiveForm);

View File

@@ -7,11 +7,23 @@ export default function PaymentReceiveFormFooter({
onSubmitClick, onSubmitClick,
onCancelClick, onCancelClick,
onClearClick, onClearClick,
paymentReceive,
}) { }) {
return ( return (
<div className={'estimate-form__floating-footer'}> <div className={'estimate-form__floating-footer'}>
<Button disabled={isSubmitting} intent={Intent.PRIMARY} type="submit"> <Button
<T id={'save_send'} /> disabled={isSubmitting}
intent={Intent.PRIMARY}
type="submit"
onClick={() => {
onSubmitClick({ redirect: true });
}}
>
{paymentReceive && paymentReceive.id ? (
<T id={'edit'} />
) : (
<T id={'save_send'} />
)}
</Button> </Button>
<Button <Button
@@ -20,6 +32,9 @@ export default function PaymentReceiveFormFooter({
className={'ml1'} className={'ml1'}
name={'save'} name={'save'}
type="submit" type="submit"
onClick={() => {
onSubmitClick({ redirect: false });
}}
> >
<T id={'save'} /> <T id={'save'} />
</Button> </Button>

View File

@@ -10,6 +10,9 @@ import {
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { useParams, useHistory } from 'react-router-dom';
import { useQuery } from 'react-query';
import moment from 'moment'; import moment from 'moment';
import { momentFormatter, compose, tansformDateValue } from 'utils'; import { momentFormatter, compose, tansformDateValue } from 'utils';
import classNames from 'classnames'; import classNames from 'classnames';
@@ -106,8 +109,55 @@ function PaymentReceiveFormHeader({
labelProp={'display_name'} labelProp={'display_name'}
/> />
</FormGroup> </FormGroup>
{/* Payment date */}
<FormGroup <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( className={classNames(
'form-group--deposit_account_id', 'form-group--deposit_account_id',
'form-group--select-list', 'form-group--select-list',
@@ -129,53 +179,37 @@ function PaymentReceiveFormHeader({
> >
<AccountsSelectList <AccountsSelectList
accounts={depositAccounts} accounts={depositAccounts}
labelInfo={<FieldRequiredHint />}
onAccountSelected={onChangeSelect('deposit_account_id')} onAccountSelected={onChangeSelect('deposit_account_id')}
defaultSelectText={<T id={'select_deposit_account'} />} defaultSelectText={<T id={'select_deposit_account'} />}
selectedAccountId={values.deposit_account_id} selectedAccountId={values.deposit_account_id}
/> />
</FormGroup> </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> </div>
{/* payment receive no */}
<FormGroup {/* Receive amount */}
label={<T id={'payment_receive_no'} />}
{/* <FormGroup
label={<T id={'receive_amount'} />}
inline={true} inline={true}
className={('form-group--payment_receive_no', Classes.FILL)}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
className={classNames('form-group--', Classes.FILL)}
intent={ intent={
errors.payment_receive_no && errors.receive_amount && touched.receive_amount && Intent.DANGER
touched.payment_receive_no &&
Intent.DANGER
} }
helperText={ helperText={
<ErrorMessage name="payment_receive_no" {...{ errors, touched }} /> <ErrorMessage name="receive_amount" {...{ errors, touched }} />
} }
> >
<InputGroup <InputGroup
intent={ intent={
errors.payment_receive_no && errors.receive_amount && touched.receive_amount && Intent.DANGER
touched.payment_receive_no &&
Intent.DANGER
} }
minimal={true} minimal={true}
{...getFieldProps('payment_receive_no')} {...getFieldProps('receive_amount')}
/> />
</FormGroup> </FormGroup> */}
{/* reference_no */} {/* reference_no */}
<FormGroup <FormGroup
label={<T id={'reference'} />} label={<T id={'reference'} />}

View File

@@ -1,8 +1,10 @@
import React, { useState, useMemo, useEffect, useCallback } from 'react'; import React, { useState, useMemo, useEffect, useCallback } from 'react';
import { Button, Intent, Position, Tooltip } from '@blueprintjs/core'; import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import DataTable from 'components/DataTable'; import { Icon, DataTable } from 'components';
import Icon from 'components/Icon'; import moment from 'moment';
import { useQuery } from 'react-query';
import { useParams, useHistory } from 'react-router-dom';
import { compose, formattedAmount, transformUpdatedRows } from 'utils'; import { compose, formattedAmount, transformUpdatedRows } from 'utils';
import { import {
@@ -10,4 +12,247 @@ import {
MoneyFieldCell, MoneyFieldCell,
EstimatesListFieldCell, EstimatesListFieldCell,
DivFieldCell, DivFieldCell,
EmptyDiv,
} from 'components/DataTableCells'; } 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 { formatMessage } = useIntl();
useEffect(() => {
setRows([...dueInvoices.map((e) => ({ ...e })), ...invoices, {}]);
}, [invoices]);
// 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);
},
[rows, 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(
withInvoices(({ dueInvoices }) => ({
dueInvoices,
})),
)(PaymentReceiveItemsTable);

View 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);

View File

@@ -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);

View File

@@ -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 { useParams, useHistory } from 'react-router-dom';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
@@ -8,7 +8,9 @@ import DashboardInsider from 'components/Dashboard/DashboardInsider';
import withCustomersActions from 'containers/Customers/withCustomersActions'; import withCustomersActions from 'containers/Customers/withCustomersActions';
import withAccountsActions from 'containers/Accounts/withAccountsActions'; import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withItemsActions from 'containers/Items/withItemsActions'; 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'; import { compose } from 'utils';
@@ -22,9 +24,16 @@ function PaymentReceives({
//#withItemsActions //#withItemsActions
requestFetchItems, requestFetchItems,
//#withInvoiceActions //#withPaymentReceivesActions
requestFetchPaymentReceive,
//#withInvoicesActions
requestFetchDueInvoices,
}) { }) {
const history = useHistory(); const history = useHistory();
const { id } = useParams();
const [customerId, setCustomerId] = useState(null);
const [payload, setPayload] = useState(false);
// Handle fetch accounts data // Handle fetch accounts data
const fetchAccounts = useQuery('accounts-list', (key) => const fetchAccounts = useQuery('accounts-list', (key) =>
@@ -32,30 +41,54 @@ function PaymentReceives({
); );
// Handle fetch Items data table or list // 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 // Handle fetch customers data table or list
const fetchCustomers = useQuery('customers-table', () => const fetchCustomers = useQuery('customers-list', () =>
requestFetchCustomers({}), 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(() => { const handleCancel = useCallback(() => {
history.goBack(); history.goBack();
}, [history]); }, [history]);
const handleCustomerChange = (customerId) => {
setCustomerId(customerId);
};
return ( return (
<DashboardInsider <DashboardInsider
loading={ loading={
fetchCustomers.isFetching || fetchCustomers.isFetching ||
fetchItems.isFetching || fetchItems.isFetching ||
fetchAccounts.isFetching fetchAccounts.isFetching ||
fetchPaymentReceive.isFetching
} }
name={'payment-receive'}
> >
<PaymentReceiveForm <PaymentReceiveForm
onFormSubmit={handleFormSubmit} onFormSubmit={handleFormSubmit}
paymentReceiveId={id}
onCancelForm={handleCancel} onCancelForm={handleCancel}
onCustomerChange={handleCustomerChange}
/> />
</DashboardInsider> </DashboardInsider>
); );
@@ -65,5 +98,6 @@ export default compose(
withCustomersActions, withCustomersActions,
withItemsActions, withItemsActions,
withAccountsActions, withAccountsActions,
// withInvoiceActions withPaymentReceivesActions,
withInvoiceActions,
)(PaymentReceives); )(PaymentReceives);

View File

@@ -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);

View File

@@ -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);
};

View File

@@ -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);
};

View File

@@ -13,7 +13,7 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(submitPaymentReceive({ form })), dispatch(submitPaymentReceive({ form })),
requestFetchPaymentReceive: (id) => dispatch(fetchPaymentReceive({ id })), requestFetchPaymentReceive: (id) => dispatch(fetchPaymentReceive({ id })),
requestEditPaymentReceive: (id, form) => requestEditPaymentReceive: (id, form) =>
dispatch(editPaymentReceive({ id, form })), dispatch(editPaymentReceive( id, form )),
requestDeletePaymentReceive: (id) => dispatch(deletePaymentReceive({ id })), requestDeletePaymentReceive: (id) => dispatch(deletePaymentReceive({ id })),
requestFetchPaymentReceiveTable: (query = {}) => requestFetchPaymentReceiveTable: (query = {}) =>
dispatch(fetchPaymentReceivesTable({ query: { ...query } })), dispatch(fetchPaymentReceivesTable({ query: { ...query } })),

View File

@@ -215,7 +215,7 @@ function ReceiptForm({
const requestForm = { ...form }; const requestForm = { ...form };
if (receipt && receipt.id) { if (receipt && receipt.id) {
requestEditReceipt(receipt.id && requestForm).then(() => { requestEditReceipt(receipt.id, requestForm).then(() => {
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage({
id: 'the_receipt_has_been_successfully_edited', id: 'the_receipt_has_been_successfully_edited',
@@ -245,6 +245,7 @@ function ReceiptForm({
} }
}, },
}); });
console.log(formik.errors, 'ERROR');
const handleDeleteFile = useCallback( const handleDeleteFile = useCallback(
(_deletedFiles) => { (_deletedFiles) => {
@@ -317,7 +318,7 @@ function ReceiptForm({
growVertically={true} growVertically={true}
{...formik.getFieldProps('statement')} {...formik.getFieldProps('statement')}
/> />
</FormGroup> </FormGroup>
</Col> </Col>
<Col> <Col>
@@ -328,7 +329,7 @@ function ReceiptForm({
hint={'Attachments: Maxiumum size: 20MB'} hint={'Attachments: Maxiumum size: 20MB'}
/> />
</Col> </Col>
</Row> </Row>
</form> </form>
<ReceiptFormFooter <ReceiptFormFooter
formik={formik} formik={formik}

View File

@@ -23,14 +23,14 @@ function Receipts({
requestFetchItems, requestFetchItems,
//#withReceiptsActions //#withReceiptsActions
requsetFetchInvoice, requestFetchReceipt,
}) { }) {
const history = useHistory(); const history = useHistory();
const { id } = useParams(); const { id } = useParams();
const fetchReceipt = useQuery( const fetchReceipt = useQuery(
['receipt', id], ['receipt', id],
(key, _id) => requsetFetchInvoice(_id), (key, _id) => requestFetchReceipt(_id),
{ enabled: !!id }, { enabled: !!id },
); );
const fetchAccounts = useQuery('accounts-list', (key) => const fetchAccounts = useQuery('accounts-list', (key) =>
@@ -60,7 +60,7 @@ function Receipts({
loading={ loading={
fetchCustomers.isFetching || fetchCustomers.isFetching ||
fetchItems.isFetching || fetchItems.isFetching ||
fetchAccounts.isFetching|| fetchAccounts.isFetching ||
fetchReceipt.isFetching fetchReceipt.isFetching
} }
name={'receipt-form'} name={'receipt-form'}

View File

@@ -17,10 +17,10 @@ export default (mapState) => {
const mapped = { const mapped = {
receiptsCurrentPage: getReceiptsItems(state, props, tableQuery), receiptsCurrentPage: getReceiptsItems(state, props, tableQuery),
receiptview:getResourceViews(state, props, 'sales_receipts'), receiptview:getResourceViews(state, props, 'sales_receipts'),
receiptItems: state.sales_receipts.items, receiptItems: state.salesReceipts.items,
receiptTableQuery: tableQuery, receiptTableQuery: tableQuery,
receiptsPagination: getReceiptPaginationMeta(state, props, tableQuery), receiptsPagination: getReceiptPaginationMeta(state, props, tableQuery),
receiptsLoading: state.sales_receipts.loading, receiptsLoading: state.salesReceipts.loading,
}; };
return mapState ? mapState(mapped, state, props) : mapped; return mapState ? mapState(mapped, state, props) : mapped;

View File

@@ -13,7 +13,7 @@ const mapDipatchToProps = (dispatch) => ({
requestEditVendor: (id, form) => dispatch(editVendor(id, form)), requestEditVendor: (id, form) => dispatch(editVendor(id, form)),
requestFetchVendorsTable: (query = {}) => requestFetchVendorsTable: (query = {}) =>
dispatch(fetchVendorsTable({ query: { ...query } })), dispatch(fetchVendorsTable({ query: { ...query } })),
requestDeleteEstimate: (id) => dispatch(deleteVendor({ id })), requestDeleteVender: (id) => dispatch(deleteVendor({ id })),
changeVendorView: (id) => changeVendorView: (id) =>
dispatch({ dispatch({

View File

@@ -73,6 +73,7 @@ export default {
new_currency: 'New Currency', new_currency: 'New Currency',
currency_name: 'Currency Name', currency_name: 'Currency Name',
currency_code: 'Currency Code', currency_code: 'Currency Code',
select_currency_code: 'select Currency Code',
edit_exchange_rate: 'Edit Exchange Rate', edit_exchange_rate: 'Edit Exchange Rate',
new_exchange_rate: 'New Exchange Rate', new_exchange_rate: 'New Exchange Rate',
delete_exchange_rate: 'Delete Exchange Rate', delete_exchange_rate: 'Delete Exchange Rate',
@@ -393,6 +394,9 @@ export default {
base_currency_: 'Base currency', base_currency_: 'Base currency',
date_format_: 'Date format', date_format_: 'Date format',
category_name_: 'Category name', category_name_: 'Category name',
sell_account_: 'Sell account',
cost_account_: 'Cost account',
inventory_account_: 'Inventory account',
view_name_: 'View name', view_name_: 'View name',
time_zone: 'Time zone', time_zone: 'Time zone',
location: 'Location', location: 'Location',
@@ -550,7 +554,6 @@ export default {
"Once you delete these journalss, you won't be able to retrieve them later. Are you sure you want to delete them?", "Once you delete these journalss, you won't be able to retrieve them later. Are you sure you want to delete them?",
once_delete_this_journal_you_will_able_to_restore_it: `Once you delete this journal, you won\'t be able to restore it later. Are you sure you want to delete ?`, once_delete_this_journal_you_will_able_to_restore_it: `Once you delete this journal, you won\'t be able to restore it later. Are you sure you want to delete ?`,
the_expense_is_already_published: 'The expense is already published.', the_expense_is_already_published: 'The expense is already published.',
accounts_without_zero_balance: 'Accounts without zero-balance', accounts_without_zero_balance: 'Accounts without zero-balance',
accounts_with_transactions: 'Accounts with transactions', accounts_with_transactions: 'Accounts with transactions',
include_accounts_once_has_transactions_on_given_date_period: include_accounts_once_has_transactions_on_given_date_period:
@@ -605,6 +608,10 @@ export default {
due_date_: 'Due date', due_date_: 'Due date',
invoice_message: 'Invoice Message', invoice_message: 'Invoice Message',
reference_no: 'Reference No', reference_no: 'Reference No',
invocie_number: 'Invoice Number',
invoice_amount: 'Invoice Amount',
amount_due: 'Amount Due',
payment_amount: 'Payment Amount',
edit_invoice: 'Edit Invoice', edit_invoice: 'Edit Invoice',
delete_invoice: 'Delete Invoice', delete_invoice: 'Delete Invoice',
new_invoice: 'New Invoice', new_invoice: 'New Invoice',
@@ -661,16 +668,26 @@ export default {
the_bill_has_been_successfully_deleted: the_bill_has_been_successfully_deleted:
'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?`, 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', 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', new_payment_receive: 'New Payment Receive',
payment_receives: 'Payment Receives', payment_receives: 'Payment Receives',
payment_receive_no: 'Payment Receive #', payment_receive_no: 'Payment Receive #',
payment_receive_no_: 'Payment receive no', 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 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', select_invoice: 'Select Invoice',
payment_mades: 'Payment Mades', payment_mades: 'Payment Mades',
subscription: 'Subscription', subscription: 'Subscription',
plan_slug: 'Plan slug', plan_slug: 'Plan slug',
billing: 'Billing', billing: 'Billing',
@@ -696,4 +713,31 @@ export default {
license_number: 'License number', license_number: 'License number',
subscribe: 'Subscribe', subscribe: 'Subscribe',
year_per: 'year', year_per: 'year',
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',
}; };

View File

@@ -160,7 +160,7 @@ export default [
}, },
// Expenses // Expenses
{ {
path: `/expenses/new`, // expenses/ path: `/expenses/new`,
component: LazyLoader({ component: LazyLoader({
loader: () => import('containers/Expenses/Expenses'), loader: () => import('containers/Expenses/Expenses'),
}), }),
@@ -228,8 +228,7 @@ export default [
breadcrumb: 'Estimates List', breadcrumb: 'Estimates List',
}, },
//Invoices // Invoices.
{ {
path: `/invoices/:id/edit`, path: `/invoices/:id/edit`,
component: LazyLoader({ component: LazyLoader({
@@ -253,7 +252,7 @@ export default [
breadcrumb: 'Invoices List', breadcrumb: 'Invoices List',
}, },
//Receipts // Sales Receipts.
{ {
path: `/receipts/:id/edit`, path: `/receipts/:id/edit`,
component: LazyLoader({ component: LazyLoader({
@@ -292,8 +291,16 @@ export default [
}), }),
breadcrumb: 'New Payment Receive', breadcrumb: 'New Payment Receive',
}, },
{
path: `/payment-receives`,
component: LazyLoader({
loader: () =>
import('containers/Sales/PaymentReceive/PaymentReceiveList'),
}),
breadcrumb: 'Payment Receive List',
},
//Bills // Bills
{ {
path: `/bills/:id/edit`, path: `/bills/:id/edit`,
component: LazyLoader({ component: LazyLoader({
@@ -322,9 +329,7 @@ export default [
}), }),
breadcrumb: 'Receipt List', breadcrumb: 'Receipt List',
}, },
// Subscription billing.
//Subscriptions
{ {
path: `/billing`, path: `/billing`,
component: LazyLoader({ component: LazyLoader({
@@ -332,6 +337,27 @@ export default [
}), }),
breadcrumb: 'New Billing', breadcrumb: 'New Billing',
}, },
// 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',
},
]; ];

View File

@@ -25,7 +25,6 @@ const reducer = createReducer(initialState, {
state.items[id] = { ...defaultBill, ..._bill, ...bill }; state.items[id] = { ...defaultBill, ..._bill, ...bill };
}, },
[t.BILLS_TABLE_LOADING]: (state, action) => { [t.BILLS_TABLE_LOADING]: (state, action) => {
const { loading } = action.payload; const { loading } = action.payload;
state.loading = loading; state.loading = loading;

View File

@@ -28,23 +28,14 @@ export const submitEstimate = ({ form }) => {
export const editEstimate = (id, form) => { export const editEstimate = (id, form) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.post(`sales/estimates/${id}`, form) ApiService.post(`sales/estimates/${id}`, form)
.then((response) => { .then((response) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {
const { response } = error; const { response } = error;
const { data } = response; const { data } = response;
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
reject(data?.errors); reject(data?.errors);
}); });
}); });
@@ -93,7 +84,7 @@ export const fetchEstimate = ({ id }) => {
export const fetchEstimatesTable = ({ query = {} }) => { export const fetchEstimatesTable = ({ query = {} }) => {
return (dispatch, getState) => return (dispatch, getState) =>
new Promise((resolve, rejcet) => { new Promise((resolve, rejcet) => {
const pageQuery = getState().sales_estimates.tableQuery; const pageQuery = getState().salesEstimates.tableQuery;
dispatch({ dispatch({
type: t.ESTIMATES_TABLE_LOADING, type: t.ESTIMATES_TABLE_LOADING,
payload: { payload: {

View File

@@ -1,20 +1,20 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors'; import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors';
const estimateTableQuery = (state) => state.sales_estimates.tableQuery; const estimateTableQuery = (state) => state.salesEstimates.tableQuery;
const estimateByIdSelector = (state, props) => const estimateByIdSelector = (state, props) =>
state.sales_estimates.items[props.estimateId]; state.salesEstimates.items[props.estimateId];
const estimatesCurrentViewSelector = (state, props) => { const estimatesCurrentViewSelector = (state, props) => {
const viewId = state.sales_estimates.currentViewId; const viewId = state.salesEstimates.currentViewId;
return state.sales_estimates.views?.[viewId]; return state.salesEstimates.views?.[viewId];
}; };
const estimateItemsSelector = (state) => state.sales_estimates.items; const estimateItemsSelector = (state) => state.salesEstimates.items;
const estimatesPageSelector = (state, props, query) => { const estimatesPageSelector = (state, props, query) => {
const viewId = state.sales_estimates.currentViewId; const viewId = state.salesEstimates.currentViewId;
return state.sales_estimates.views?.[viewId]?.pages?.[query.page]; return state.salesEstimates.views?.[viewId]?.pages?.[query.page];
}; };
export const getEstimatesTableQueryFactory = () => export const getEstimatesTableQueryFactory = () =>

View File

@@ -13,6 +13,7 @@ export const fetchExchangeRates = () => {
}); });
ApiService.get('exchange_rates') ApiService.get('exchange_rates')
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: t.EXCHANGE_RATE_LIST_SET, type: t.EXCHANGE_RATE_LIST_SET,
exchange_rates: response.data.exchange_rates.results, exchange_rates: response.data.exchange_rates.results,
@@ -35,20 +36,18 @@ export const fetchExchangeRates = () => {
export const submitExchangeRate = ({ form }) => { export const submitExchangeRate = ({ form }) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
ApiService.post('exchange_rates', form).then((response) => { ApiService.post('exchange_rates', form)
resolve(response); .then((response) => {
}).catch((error)=>{ resolve(response);
const {response} = error })
const {data} = response; .catch((error) => {
reject(data?.errors) const { response } = error;
}) const { data } = response;
reject(data?.errors);
});
}); });
}; };
// export const deleteExchangeRate = (id) => {
// return (dispatch) => ApiService.delete(`exchange_rates/${id}`);
// }
export const deleteExchangeRate = (id) => { export const deleteExchangeRate = (id) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {

View File

@@ -1,10 +1,20 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { pickItemsFromIds, getItemById } from 'store/selectors';
const exchangeRateItemsSelector = state => state.exchangeRates.exchangeRates; const exchangeRateItemsSelector = (state) => state.exchangeRates.exchangeRates;
const exchangeRateIdPropSelector = (state, props) => props.exchangeRateId;
export const getExchangeRatesList = createSelector( export const getExchangeRatesList = createSelector(
exchangeRateItemsSelector, exchangeRateItemsSelector,
(exchangeRateItems) => { (exchangeRateItems) => {
return Object.values(exchangeRateItems); return Object.values(exchangeRateItems);
}, },
) );
export const getExchangeRateById = createSelector(
exchangeRateItemsSelector,
exchangeRateIdPropSelector,
(exchangeRates, exchangeRateId) => {
return getItemById(exchangeRates, exchangeRateId);
},
);

View File

@@ -4,25 +4,13 @@ import t from 'store/types';
export const submitInvoice = ({ form }) => { export const submitInvoice = ({ form }) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
// @todo remove dead-code
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.post('sales/invoices', form) ApiService.post('sales/invoices', form)
.then((response) => { .then((response) => {
// @todo remove dead-code
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {
const { response } = error; const { response } = error;
const { data } = response; const { data } = response;
// @todo remove dead-code
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
reject(data?.errors); reject(data?.errors);
}); });
@@ -54,20 +42,12 @@ export const editInvoice = (id, form) => {
}); });
ApiService.post(`sales/invoices/${id}`, form) ApiService.post(`sales/invoices/${id}`, form)
.then((response) => { .then((response) => {
// @todo remove dead-code
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {
const { response } = error; const { response } = error;
const { data } = response; const { data } = response;
// @todo remove dead-code
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
reject(data?.errors); reject(data?.errors);
}); });
}); });
@@ -76,7 +56,7 @@ export const editInvoice = (id, form) => {
export const fetchInvoicesTable = ({ query } = {}) => { export const fetchInvoicesTable = ({ query } = {}) => {
return (dispatch, getState) => return (dispatch, getState) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const pageQuery = getState().sales_invoices.tableQuery; const pageQuery = getState().salesInvoices.tableQuery;
dispatch({ dispatch({
type: t.INVOICES_TABLE_LOADING, type: t.INVOICES_TABLE_LOADING,
payload: { payload: {
@@ -95,6 +75,7 @@ export const fetchInvoicesTable = ({ query } = {}) => {
customViewId: response.data.customViewId || -1, customViewId: response.data.customViewId || -1,
}, },
}); });
dispatch({ dispatch({
type: t.INVOICES_ITEMS_SET, type: t.INVOICES_ITEMS_SET,
payload: { payload: {
@@ -143,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);
});
});
};

View File

@@ -12,6 +12,7 @@ const initialState = {
page_size: 5, page_size: 5,
page: 1, page: 1,
}, },
dueInvoices: {},
}; };
const defaultInvoice = { const defaultInvoice = {
@@ -22,13 +23,13 @@ const reducer = createReducer(initialState, {
[t.INVOICE_SET]: (state, action) => { [t.INVOICE_SET]: (state, action) => {
const { id, sale_invoice } = action.payload; const { id, sale_invoice } = action.payload;
const _invoice = state.items[id] || {}; const _invoice = state.items[id] || {};
state.items[id] = { ...defaultInvoice, ..._invoice, ...sale_invoice }; state.items[id] = { ...defaultInvoice, ..._invoice, ...sale_invoice };
}, },
[t.INVOICES_ITEMS_SET]: (state, action) => { [t.INVOICES_ITEMS_SET]: (state, action) => {
const { sales_invoices } = action.payload; const { sales_invoices } = action.payload;
const _invoices = {}; const _invoices = {};
sales_invoices.forEach((invoice) => { sales_invoices.forEach((invoice) => {
_invoices[invoice.id] = { _invoices[invoice.id] = {
...defaultInvoice, ...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); export default createTableQueryReducers('sales_invoices', reducer);

View File

@@ -1,23 +1,26 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors'; import {
pickItemsFromIds,
paginationLocationQuery,
getItemById,
} from 'store/selectors';
const invoiceTableQuery = (state) => state.sales_invoices.tableQuery; const invoiceTableQuery = (state) => state.salesInvoices.tableQuery;
const invoicesByIdSelector = (state, props) => const invoicesByIdSelector = (state, props) =>
state.sales_invoices.items[props.invoiceId]; state.salesInvoices.items[props.invoiceId];
const invoicesPaginationSelector = (state, props) => { const invoicesPaginationSelector = (state, props) => {
const viewId = state.sales_invoices.currentViewId; const viewId = state.salesInvoices.currentViewId;
return state.sales_invoices.views?.[viewId]; return state.salesInvoices.views?.[viewId];
}; };
const invoicesPageSelector = (state, props, query) => { const invoicesPageSelector = (state, props, query) => {
const viewId = state.sales_invoices.currentViewId; const viewId = state.salesInvoices.currentViewId;
return state.sales_invoices.views?.[viewId]?.pages?.[query.page]; return state.salesInvoices.views?.[viewId]?.pages?.[query.page];
}; };
const invoicesItemsSelector = (state) => state.sales_invoices.items; const invoicesItemsSelector = (state) => state.salesInvoices.items;
export const getInvoiceTableQueryFactory = () => export const getInvoiceTableQueryFactory = () =>
createSelector( createSelector(
@@ -29,7 +32,7 @@ export const getInvoiceTableQueryFactory = () =>
...tableQuery, ...tableQuery,
}; };
}, },
); );
export const getInvoiceCurrentPageFactory = () => export const getInvoiceCurrentPageFactory = () =>
createSelector( createSelector(
@@ -51,3 +54,18 @@ export const getInvoicePaginationMetaFactory = () =>
createSelector(invoicesPaginationSelector, (invoicePage) => { createSelector(invoicesPaginationSelector, (invoicePage) => {
return invoicePage?.paginationMeta || {}; 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) || []
: [];
},
);

View File

@@ -9,4 +9,6 @@ export default {
INVOICES_PAGINATION_SET: 'INVOICES_PAGINATION_SET', INVOICES_PAGINATION_SET: 'INVOICES_PAGINATION_SET',
INVOICES_PAGE_SET: 'INVOICES_PAGE_SET', INVOICES_PAGE_SET: 'INVOICES_PAGE_SET',
INVOICES_ITEMS_SET: 'INVOICES_ITEMS_SET', INVOICES_ITEMS_SET: 'INVOICES_ITEMS_SET',
DUE_INVOICES_SET: 'DUE_INVOICES_SET',
RELOAD_INVOICES: 'RELOAD_INVOICES',
}; };

View 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);
});
});
};

View 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);

View 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;
});

View 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',
};

View File

@@ -4,22 +4,14 @@ import t from 'store/types';
export const submitPaymentReceive = ({ form }) => { export const submitPaymentReceive = ({ form }) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.post('sales/payment_receives', form) ApiService.post('sales/payment_receives', form)
.then((response) => { .then((response) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {
const { response } = error; const { response } = error;
const { data } = response; const { data } = response;
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
reject(data?.errors); reject(data?.errors);
}); });
}); });
@@ -28,22 +20,14 @@ export const submitPaymentReceive = ({ form }) => {
export const editPaymentReceive = (id, form) => { export const editPaymentReceive = (id, form) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, rejcet) => { new Promise((resolve, rejcet) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.post(`sales/payment_receives/${id}`, form) ApiService.post(`sales/payment_receives/${id}`, form)
.then((response) => { .then((response) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {
const { response } = error; const { response } = error;
const { data } = response; const { data } = response;
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
rejcet(data?.errors); rejcet(data?.errors);
}); });
}); });
@@ -52,9 +36,9 @@ export const editPaymentReceive = (id, form) => {
export const deletePaymentReceive = ({ id }) => { export const deletePaymentReceive = ({ id }) => {
return (dispatch) => return (dispatch) =>
new Promise((resovle, reject) => { new Promise((resovle, reject) => {
ApiService.delete(`payment_receives/${id}`) ApiService.delete(`sales/payment_receives/${id}`)
.then((response) => { .then((response) => {
dispatch({ type: t.PAYMENT_RECEIVE_DELETE }); dispatch({ type: t.PAYMENT_RECEIVE_DELETE, payload: { id } });
resovle(response); resovle(response);
}) })
.catch((error) => { .catch((error) => {
@@ -66,21 +50,31 @@ export const deletePaymentReceive = ({ id }) => {
export const fetchPaymentReceive = ({ id }) => { export const fetchPaymentReceive = ({ id }) => {
return (dispatch) => return (dispatch) =>
new Promise((resovle, reject) => { new Promise((resovle, reject) => {
ApiService.get(`payment_receives/${id}`) ApiService.get(`sales/payment_receives/${id}`, {})
.then((response) => { .then((response) => {
dispatch({
type: t.RELOAD_INVOICES,
payload: {
sales_invoices: response.data.paymentReceive.entries.map(
(e) => e.invoice,
),
},
});
dispatch({ dispatch({
type: t.PAYMENT_RECEIVE_SET, type: t.PAYMENT_RECEIVE_SET,
payload: { payload: {
id, id,
payment_receive: response.data.payment_receive, paymentReceive: response.data.paymentReceive,
}, },
}); });
resovle(response); resovle(response);
}) })
.catch((error) => { .catch((error) => {
const { response } = error; // const { response } = error;
const { data } = response; // const { data } = response;
reject(data?.errors); // reject(data?.errors);
reject(error);
}); });
}); });
}; };
@@ -88,7 +82,7 @@ export const fetchPaymentReceive = ({ id }) => {
export const fetchPaymentReceivesTable = ({ query = {} }) => { export const fetchPaymentReceivesTable = ({ query = {} }) => {
return (dispatch, getState) => return (dispatch, getState) =>
new Promise((resolve, rejcet) => { new Promise((resolve, rejcet) => {
const pageQuery = getState().payment_receive.tableQuery; const pageQuery = getState().paymentReceives.tableQuery;
dispatch({ dispatch({
type: t.PAYMENT_RECEIVES_TABLE_LOADING, type: t.PAYMENT_RECEIVES_TABLE_LOADING,
@@ -96,12 +90,12 @@ export const fetchPaymentReceivesTable = ({ query = {} }) => {
loading: true, loading: true,
}, },
}); });
ApiService.get('payment_receives', { ApiService.get('sales/payment_receives', {
params: { ...pageQuery, ...query }, params: { ...pageQuery, ...query },
}) })
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: t.RECEIPTS_PAGE_SET, type: t.PAYMENT_RECEIVES_PAGE_SET,
payload: { payload: {
payment_receives: response.data.payment_receives.results, payment_receives: response.data.payment_receives.results,
pagination: response.data.payment_receives.pagination, pagination: response.data.payment_receives.pagination,

View File

@@ -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);

View File

@@ -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),
)
: [];
},
);

View File

@@ -1,7 +1,7 @@
export default { export default {
PAYMENT_RECEIVE_LIST_SET: 'PAYMENT_RECEIVE_LIST_SET', PAYMENT_RECEIVE_LIST_SET: 'PAYMENT_RECEIVE_LIST_SET',
PAYMENT_RECEIVE_SET: 'PAYMENT_RECEIVE_SET',
PAYMENT_RECEIVE_DELETE: 'PAYMENT_RECEIVE_DELETE', PAYMENT_RECEIVE_DELETE: 'PAYMENT_RECEIVE_DELETE',
PAYMENT_RECEIVE_SET: 'PAYMENT_RECEIVE_SET',
PAYMENT_RECEIVE_SET_CURRENT_VIEW: 'PAYMENT_RECEIVE_SET_CURRENT_VIEW', PAYMENT_RECEIVE_SET_CURRENT_VIEW: 'PAYMENT_RECEIVE_SET_CURRENT_VIEW',
PAYMENT_RECEIVE_TABLE_QUERIES_ADD: 'PAYMENT_RECEIVE_TABLE_QUERIES_ADD', PAYMENT_RECEIVE_TABLE_QUERIES_ADD: 'PAYMENT_RECEIVE_TABLE_QUERIES_ADD',
PAYMENT_RECEIVES_TABLE_LOADING: 'PAYMENT_RECEIVES_TABLE_LOADING', PAYMENT_RECEIVES_TABLE_LOADING: 'PAYMENT_RECEIVES_TABLE_LOADING',

View File

@@ -8,37 +8,38 @@ export const submitItemCategory = ({ form }) => {
}; };
export const fetchItemCategories = ({ query }) => { export const fetchItemCategories = ({ query }) => {
return (dispatch, getState) => new Promise((resolve, reject) => { return (dispatch, getState) =>
dispatch({ new Promise((resolve, reject) => {
type: t.SET_DASHBOARD_REQUEST_LOADING, dispatch({
}); type: t.SET_DASHBOARD_REQUEST_LOADING,
dispatch({
type: t.ITEM_CATEGORIES_TABLE_LOADING,
payload: {
loading: true,
}
});
ApiService.get('item_categories', { params: { ...query } })
.then((response) => {
dispatch({
type: t.ITEMS_CATEGORY_LIST_SET,
categories: response.data.categories,
});
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
dispatch({
type: t.ITEM_CATEGORIES_TABLE_LOADING,
payload: {
loading: false,
}
});
resolve(response);
})
.catch((error) => {
reject(error);
}); });
}); dispatch({
type: t.ITEM_CATEGORIES_TABLE_LOADING,
payload: {
loading: true,
},
});
ApiService.get('item_categories', { params: { ...query } })
.then((response) => {
dispatch({
type: t.ITEMS_CATEGORY_LIST_SET,
categories: response.data.categories,
});
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
dispatch({
type: t.ITEM_CATEGORIES_TABLE_LOADING,
payload: {
loading: false,
},
});
resolve(response);
})
.catch((error) => {
reject(error);
});
});
}; };
export const editItemCategory = (id, form) => { export const editItemCategory = (id, form) => {
@@ -46,19 +47,13 @@ export const editItemCategory = (id, form) => {
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
ApiService.post(`item_categories/${id}`, form) ApiService.post(`item_categories/${id}`, form)
.then((response) => { .then((response) => {
dispatch({ type: t.CLEAR_CATEGORY_FORM_ERRORS });
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {
const { response } = error; const { response } = error;
const { data } = response; const { data } = response;
const { errors } = data;
dispatch({ type: t.CLEAR_CATEGORY_FORM_ERRORS }); reject(data?.errors);
if (errors) {
dispatch({ type: t.CLEAR_CATEGORY_FORM_ERRORS, errors });
}
reject(error);
}); });
}); });
}; };
@@ -81,15 +76,18 @@ export const deleteItemCategory = (id) => {
}; };
export const deleteBulkItemCategories = ({ ids }) => { export const deleteBulkItemCategories = ({ ids }) => {
return dispatch => new Promise((resolve, reject) => { return (dispatch) =>
ApiService.delete(`item_categories/bulk`, { params: { ids }}).then((response) => { new Promise((resolve, reject) => {
dispatch({ ApiService.delete(`item_categories/bulk`, { params: { ids } })
type: t.ITEM_CATEGORIES_BULK_DELETE, .then((response) => {
payload: { ids } dispatch({
}); type: t.ITEM_CATEGORIES_BULK_DELETE,
resolve(response); payload: { ids },
}).catch((error) => { });
reject(error); resolve(response);
})
.catch((error) => {
reject(error);
});
}); });
}); };
};

View File

@@ -3,6 +3,7 @@ export default {
ITEMS_CATEGORY_DATA_TABLE: 'ITEMS_CATEGORY_DATA_TABLE', ITEMS_CATEGORY_DATA_TABLE: 'ITEMS_CATEGORY_DATA_TABLE',
CATEGORY_DELETE: 'CATEGORY_DELETE', CATEGORY_DELETE: 'CATEGORY_DELETE',
ITEM_CATEGORIES_TABLE_LOADING: 'ITEM_CATEGORIES_TABLE_LOADING', 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',
}; };

View File

@@ -45,22 +45,14 @@ export const deleteReceipt = ({ id }) => {
export const editReceipt = (id, form) => { export const editReceipt = (id, form) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, rejcet) => { new Promise((resolve, rejcet) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.post(`sales/receipts/${id}`, form) ApiService.post(`sales/receipts/${id}`, form)
.then((response) => { .then((response) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {
const { response } = error; const { response } = error;
const { data } = response; const { data } = response;
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
rejcet(data?.errors); rejcet(data?.errors);
}); });
}); });
@@ -91,7 +83,7 @@ export const fetchReceipt = ({ id }) => {
export const fetchReceiptsTable = ({ query = {} }) => { export const fetchReceiptsTable = ({ query = {} }) => {
return (dispatch, getState) => return (dispatch, getState) =>
new Promise((resolve, rejcet) => { new Promise((resolve, rejcet) => {
const pageQuery = getState().sales_receipts.tableQuery; const pageQuery = getState().salesReceipts.tableQuery;
dispatch({ dispatch({
type: t.RECEIPTS_TABLE_LOADING, type: t.RECEIPTS_TABLE_LOADING,
payload: { payload: {

View File

@@ -2,20 +2,20 @@ import { createSelector } from '@reduxjs/toolkit';
import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors'; import { pickItemsFromIds, paginationLocationQuery } from 'store/selectors';
const receiptsPageSelector = (state, props, query) => { const receiptsPageSelector = (state, props, query) => {
const viewId = state.sales_receipts.currentViewId; const viewId = state.salesReceipts.currentViewId;
return state.sales_receipts.views?.[viewId]?.pages?.[query.page]; return state.salesReceipts.views?.[viewId]?.pages?.[query.page];
}; };
const receiptsPaginationSelector = (state, props) => { const receiptsPaginationSelector = (state, props) => {
const viewId = state.sales_receipts.currentViewId; const viewId = state.salesReceipts.currentViewId;
return state.sales_receipts.views?.[viewId]; return state.salesReceipts.views?.[viewId];
}; };
const receiptItemsSelector = (state) => state.sales_receipts.items; const receiptItemsSelector = (state) => state.salesReceipts.items;
const receiptTableQuery = (state) => state.sales_receipts.tableQuery; const receiptTableQuery = (state) => state.salesReceipts.tableQuery;
const receiptByIdSelector = (state, props) => state.sales_receipts.items[props.receiptId]; const receiptByIdSelector = (state, props) => state.salesReceipts.items[props.receiptId];
export const getReceiptCurrentPageFactory = () => export const getReceiptCurrentPageFactory = () =>

View File

@@ -18,11 +18,13 @@ import globalSearch from './search/search.reducer';
import exchangeRates from './ExchangeRate/exchange.reducer'; import exchangeRates from './ExchangeRate/exchange.reducer';
import globalErrors from './globalErrors/globalErrors.reducer'; import globalErrors from './globalErrors/globalErrors.reducer';
import customers from './customers/customers.reducer'; import customers from './customers/customers.reducer';
import sales_estimates from './Estimate/estimates.reducer'; import salesEstimates from './Estimate/estimates.reducer';
import sales_invoices from './Invoice/invoices.reducer'; import salesInvoices from './Invoice/invoices.reducer';
import sales_receipts from './receipt/receipt.reducer'; import salesReceipts from './receipt/receipt.reducer';
import bills from './Bills/bills.reducer'; import bills from './Bills/bills.reducer';
import vendors from './vendors/vendors.reducer'; import vendors from './vendors/vendors.reducer';
import paymentReceives from './PaymentReceive/paymentReceive.reducer';
import paymentMades from './PaymentMades/paymentMade.reducer';
export default combineReducers({ export default combineReducers({
authentication, authentication,
@@ -44,10 +46,11 @@ export default combineReducers({
globalErrors, globalErrors,
customers, customers,
// @todo camelCase salesEstimates,
sales_estimates, salesInvoices,
sales_invoices, salesReceipts,
sales_receipts,
bills, bills,
vendors, vendors,
paymentReceives,
paymentMades
}); });

View File

@@ -21,9 +21,9 @@ import estimates from './Estimate/estimates.types';
import invoices from './Invoice/invoices.types'; import invoices from './Invoice/invoices.types';
import receipts from './receipt/receipt.type'; import receipts from './receipt/receipt.type';
import bills from './Bills/bills.type'; import bills from './Bills/bills.type';
import paymentReceives from './PaymentReceive/paymentReceive.type';
import vendors from './vendors/vendors.types'; import vendors from './vendors/vendors.types';
import billing from './billing/Billing.type'; import paymentReceives from './PaymentReceive/paymentReceive.type';
import paymentMades from './PaymentMades/paymentMade.type';
export default { export default {
...authentication, ...authentication,
@@ -45,11 +45,11 @@ export default {
...register, ...register,
...exchangeRate, ...exchangeRate,
...customer, ...customer,
...vendors,
...estimates, ...estimates,
...invoices, ...invoices,
...receipts, ...receipts,
...bills, ...bills,
...paymentReceives, ...paymentReceives,
...vendors, ...paymentMades,
...billing
}; };

View File

@@ -9,10 +9,7 @@ export const fetchVendorsTable = ({ query }) => {
type: t.VENDORS_TABLE_LOADING, type: t.VENDORS_TABLE_LOADING,
payload: { loading: true }, payload: { loading: true },
}); });
// @todo remove dead-code.
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.get(`vendors`, { params: { ...pageQuery, ...query } }) ApiService.get(`vendors`, { params: { ...pageQuery, ...query } })
.then((response) => { .then((response) => {
dispatch({ dispatch({
@@ -40,10 +37,7 @@ export const fetchVendorsTable = ({ query }) => {
type: t.VENDORS_TABLE_LOADING, type: t.VENDORS_TABLE_LOADING,
payload: { loading: false }, payload: { loading: false },
}); });
// @todo remove dead-lock.
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {
@@ -55,26 +49,16 @@ export const fetchVendorsTable = ({ query }) => {
export const editVendor = ({ form, id }) => { export const editVendor = ({ form, id }) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
// @todo remove dread-code.
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.post(`vendors/${id}`, form) ApiService.post(`vendors/${id}`, form)
.then((response) => { .then((response) => {
// @todo remove dread-code.
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {
const { response } = error; const { response } = error;
const { data } = response; const { data } = response;
// @todo remove dread-code.
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
reject(data?.errors); reject(data?.errors);
}); });
}); });

View File

@@ -57,20 +57,18 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
@import 'pages/item-category'; @import 'pages/item-category';
@import 'pages/items'; @import 'pages/items';
@import 'pages/items-categories'; @import 'pages/items-categories';
@import 'pages/invite-form.scss'; @import 'pages/invite-form';
@import 'pages/currency'; @import 'pages/currency';
@import 'pages/invite-user.scss'; @import 'pages/invite-user';
@import 'pages/exchange-rate.scss'; @import 'pages/exchange-rate';
@import 'pages/customer.scss'; @import 'pages/customer';
@import 'pages/estimates'; @import 'pages/billing';
@import 'pages/receipts';
@import 'pages/invoices';
@import 'pages/billing.scss';
// Views // Views
@import 'views/filter-dropdown'; @import 'views/filter-dropdown';
@import 'views/sidebar'; @import 'views/sidebar';
@import 'pages/estimate';
.App { .App {
min-width: 960px; min-width: 960px;
} }

View File

@@ -0,0 +1,379 @@
.estimate-form {
padding-bottom: 30px;
display: flex;
flex-direction: column;
.bp3-form-group {
width: 100%;
margin: 25px 20px 15px;
}
.bp3-label {
margin: 0 20px 0;
font-weight: 500;
font-size: 13px;
color: #444;
width: 130px;
}
.bp3-form-content {
width: 35%;
.bp3-button:not([class*='bp3-intent-']):not(.bp3-minimal) {
width: 120%;
}
}
&__table {
padding: 15px 15px 0;
.bp3-form-group {
margin-bottom: 0;
}
.table {
border: 1px dotted rgb(195, 195, 195);
border-bottom: transparent;
border-left: transparent;
.th,
.td {
border-left: 1px dotted rgb(195, 195, 195);
&.index {
> span,
> div {
text-align: center;
width: 100%;
font-weight: 500;
}
}
}
.thead {
.tr .th {
padding: 10px 10px;
background-color: #f2f5fa;
font-size: 14px;
font-weight: 500;
color: #333;
}
}
.tbody {
.tr .td {
padding: 7px;
border-bottom: 1px dotted rgb(195, 195, 195);
min-height: 46px;
&.index {
background-color: #f2f5fa;
text-align: center;
> span {
margin-top: auto;
margin-bottom: auto;
}
}
}
.tr {
.bp3-form-group .bp3-input,
.form-group--select-list .bp3-button {
border-radius: 3px;
padding-left: 8px;
padding-right: 8px;
}
.bp3-form-group:not(.bp3-intent-danger) .bp3-input,
.form-group--select-list:not(.bp3-intent-danger) .bp3-button {
border-color: #e5e5e5;
}
&:last-of-type {
.td {
border-bottom: transparent;
.bp3-button,
.bp3-input-group {
display: none;
}
}
}
.td.actions {
.bp3-button {
background-color: transparent;
color: #e68f8e;
&:hover {
color: #c23030;
}
}
}
&.row--total {
.td.amount {
font-weight: bold;
}
}
}
}
.th {
color: #444;
font-weight: 600;
border-bottom: 1px dotted #666;
}
.td {
border-bottom: 1px dotted #999;
&.description {
.bp3-form-group {
width: 100%;
}
}
}
.actions.td {
.bp3-button {
background: transparent;
margin: 0;
}
}
}
}
&__floating-footer {
position: fixed;
bottom: 0;
width: 100%;
background: #fff;
padding: 18px 18px;
border-top: 1px solid #ececec;
.has-mini-sidebar & {
left: 50px;
}
}
.bp3-button {
&.button--clear-lines {
background-color: #fcefef;
}
}
.button--clear-lines,
.button--new-line {
padding-left: 14px;
padding-right: 14px;
}
.dropzone-container {
margin-top: 0;
align-self: flex-end;
}
.dropzone {
width: 300px;
height: 75px;
}
.form-group--description {
.bp3-label {
font-weight: 500;
font-size: 13px;
color: #444;
}
.bp3-form-content {
// width: 280px;
textarea {
width: 450px;
min-height: 75px;
}
}
}
}
// .estimate-form {
// padding-bottom: 30px;
// display: flex;
// flex-direction: column;
// .bp3-form-group {
// margin: 25px 20px 15px;
// width: 100%;
// .bp3-label {
// font-weight: 500;
// font-size: 13px;
// color: #444;
// width: 130px;
// }
// .bp3-form-content {
// // width: 400px;
// width: 45%;
// }
// }
// // .expense-form-footer {
// // display: flex;
// // padding: 30px 25px 0;
// // justify-content: space-between;
// // }
// &__primary-section {
// background: #fbfbfb;
// }
// &__table {
// padding: 15px 15px 0;
// .bp3-form-group {
// margin-bottom: 0;
// }
// .table {
// border: 1px dotted rgb(195, 195, 195);
// border-bottom: transparent;
// border-left: transparent;
// .th,
// .td {
// border-left: 1px dotted rgb(195, 195, 195);
// &.index {
// > span,
// > div {
// text-align: center;
// width: 100%;
// font-weight: 500;
// }
// }
// }
// .thead {
// .tr .th {
// padding: 10px 10px;
// background-color: #f2f5fa;
// font-size: 14px;
// font-weight: 500;
// color: #333;
// }
// }
// .tbody {
// .tr .td {
// padding: 7px;
// border-bottom: 1px dotted rgb(195, 195, 195);
// min-height: 46px;
// &.index {
// background-color: #f2f5fa;
// text-align: center;
// > span {
// margin-top: auto;
// margin-bottom: auto;
// }
// }
// }
// .tr {
// .bp3-form-group .bp3-input,
// .form-group--select-list .bp3-button {
// border-radius: 3px;
// padding-left: 8px;
// padding-right: 8px;
// }
// .bp3-form-group:not(.bp3-intent-danger) .bp3-input,
// .form-group--select-list:not(.bp3-intent-danger) .bp3-button {
// border-color: #e5e5e5;
// }
// &:last-of-type {
// .td {
// border-bottom: transparent;
// .bp3-button,
// .bp3-input-group {
// display: none;
// }
// }
// }
// .td.actions {
// .bp3-button {
// background-color: transparent;
// color: #e68f8e;
// &:hover {
// color: #c23030;
// }
// }
// }
// &.row--total {
// .td.amount {
// font-weight: bold;
// }
// }
// }
// }
// .th {
// color: #444;
// font-weight: 600;
// border-bottom: 1px dotted #666;
// }
// .td {
// border-bottom: 1px dotted #999;
// &.description {
// .bp3-form-group {
// width: 100%;
// }
// }
// }
// .actions.td {
// .bp3-button {
// background: transparent;
// margin: 0;
// }
// }
// }
// }
// &__floating-footer {
// position: fixed;
// bottom: 0;
// width: 100%;
// background: #fff;
// padding: 18px 18px;
// border-top: 1px solid #ececec;
// .has-mini-sidebar & {
// left: 50px;
// }
// }
// .bp3-button {
// &.button--clear-lines {
// background-color: #fcefef;
// }
// }
// .button--clear-lines,
// .button--new-line {
// padding-left: 14px;
// padding-right: 14px;
// }
// .dropzone-container {
// margin-top: 0;
// align-self: flex-end;
// }
// .dropzone {
// width: 300px;
// height: 75px;
// }
// .form-group--description {
// .bp3-label {
// font-weight: 500;
// font-size: 13px;
// color: #444;
// }
// .bp3-form-content {
// // width: 280px;
// textarea {
// width: 450px;
// min-height: 75px;
// }
// }
// }
// }

View File

@@ -1,159 +0,0 @@
.page-form{
padding: 15px;
.bp3-form-group{
.bp3-label{
width: 100%;
max-width: 170px;
min-width: 140px;
}
.bp3-form-content{
width: 100%;
max-width: 300px;
}
}
&__primary-section {
background: #fbfbfb;
margin: -15px -15px 25px;
padding: 30px 15px 10px;
}
.form-group{
&--customer{
.bp3-form-content{
max-width: 420px;
}
}
}
}
.datatable-editor {
padding: 15px 15px 0;
&-actions{
padding: 0 15px;
.bp3-button.button--clear-lines {
background-color: #fcefef;
}
}
.bp3-form-group {
margin-bottom: 0;
}
.table {
border: 1px dotted rgb(195, 195, 195);
border-bottom: transparent;
border-left: transparent;
.th,
.td {
border-left: 1px dotted rgb(195, 195, 195);
&.index {
> span,
> div {
text-align: center;
width: 100%;
font-weight: 500;
}
}
}
.thead {
.tr .th {
padding: 10px 10px;
background-color: #f2f5fa;
font-size: 14px;
font-weight: 500;
color: #333;
}
}
.tbody {
.tr .td {
padding: 7px;
border-bottom: 1px dotted rgb(195, 195, 195);
min-height: 46px;
&.index {
background-color: #f2f5fa;
text-align: center;
> span {
margin-top: auto;
margin-bottom: auto;
}
}
}
.tr {
.bp3-form-group .bp3-input,
.form-group--select-list .bp3-button {
border-radius: 3px;
padding-left: 8px;
padding-right: 8px;
}
.bp3-form-group:not(.bp3-intent-danger) .bp3-input,
.form-group--select-list:not(.bp3-intent-danger) .bp3-button {
border-color: #E5E5E5;
}
&:last-of-type {
.td {
border-bottom: transparent;
.bp3-button,
.bp3-input-group {
display: none;
}
}
}
.td.actions {
.bp3-button {
background-color: transparent;
svg{
color: #e68f8e;
}
&:hover svg{
color: #c23030;
}
}
}
&.row--total {
.td.amount{
font-weight: bold;
}
}
}
}
.th {
color: #444;
font-weight: 600;
border-bottom: 1px dotted #666;
}
.td {
border-bottom: 1px dotted #999;
&.description{
.bp3-form-group{
width: 100%;
}
}
}
.actions.td {
.bp3-button {
background: transparent;
margin: 0;
}
}
}
}