mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 15:50:32 +00:00
feat: remove SET_DASHBOARD_REQUEST_LOADING reducer.
feat: fix dropdown filter. feat: fix fetch resource data.
This commit is contained in:
@@ -1,13 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {Dialog, Spinner, Classes} from '@blueprintjs/core';
|
|
||||||
|
|
||||||
export default function DialogComponent(props) {
|
|
||||||
const loadingContent = (
|
|
||||||
<div className={Classes.DIALOG_BODY}><Spinner size={30} /></div>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Dialog {...props}>
|
|
||||||
{props.isLoading ? loadingContent : props.children}
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
22
client/src/components/Dialog/Dialog.js
Normal file
22
client/src/components/Dialog/Dialog.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Dialog } from '@blueprintjs/core';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function DialogComponent(props) {
|
||||||
|
const { name, children, closeDialog, onClose } = props;
|
||||||
|
|
||||||
|
const handleClose = (event) => {
|
||||||
|
closeDialog(name)
|
||||||
|
onClose && onClose(event);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Dialog {...props} onClose={handleClose}>
|
||||||
|
{ children }
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
)(DialogComponent);
|
||||||
15
client/src/components/Dialog/DialogContent.js
Normal file
15
client/src/components/Dialog/DialogContent.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Spinner, Classes } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
export default function DialogContent(props) {
|
||||||
|
const { isLoading, children } = props;
|
||||||
|
|
||||||
|
const loadingContent = (
|
||||||
|
<div className={Classes.DIALOG_BODY}><Spinner size={30} /></div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isLoading ? loadingContent : children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
client/src/components/Dialog/DialogSuspense.js
Normal file
18
client/src/components/Dialog/DialogSuspense.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React, { Suspense } from 'react';
|
||||||
|
import { Classes, Spinner } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
function LoadingContent() {
|
||||||
|
return (<div className={Classes.DIALOG_BODY}><Spinner size={30} /></div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DialogSuspense({
|
||||||
|
children
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<LoadingContent /> }>
|
||||||
|
<div className={'dialog__suspense-wrapper'}>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
isDialogOpenFactory,
|
isDialogOpenFactory,
|
||||||
getDialogPayloadFactory,
|
getDialogPayloadFactory,
|
||||||
} from 'store/dashboard/dashboard.selectors';
|
} from 'store/dashboard/dashboard.selectors';
|
||||||
|
|
||||||
export default (mapState, dialogName) => {
|
export default (mapState) => {
|
||||||
const isDialogOpen = isDialogOpenFactory(dialogName);
|
const isDialogOpen = isDialogOpenFactory();
|
||||||
const getDialogPayload = getDialogPayloadFactory(dialogName);
|
const getDialogPayload = getDialogPayloadFactory();
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const mapped = {
|
const mapped = {
|
||||||
dialogName,
|
|
||||||
isOpen: isDialogOpen(state, props),
|
isOpen: isDialogOpen(state, props),
|
||||||
payload: getDialogPayload(state, props),
|
payload: getDialogPayload(state, props),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
import React from 'react';
|
import React, { lazy } from 'react';
|
||||||
|
|
||||||
import AccountFormDialog from 'containers/Dialogs/AccountFormDialog';
|
import AccountFormDialog from 'containers/Dialogs/AccountFormDialog';
|
||||||
import UserFormDialog from 'containers/Dialogs/UserFormDialog';
|
// import UserFormDialog from 'containers/Dialogs/UserFormDialog';
|
||||||
import ItemCategoryDialog from 'containers/Dialogs/ItemCategoryDialog';
|
// import ItemCategoryDialog from 'containers/Dialogs/ItemCategoryDialog';
|
||||||
import CurrencyDialog from 'containers/Dialogs/CurrencyDialog';
|
// import CurrencyDialog from 'containers/Dialogs/CurrencyDialog';
|
||||||
import InviteUserDialog from 'containers/Dialogs/InviteUserDialog';
|
// import InviteUserDialog from 'containers/Dialogs/InviteUserDialog';
|
||||||
import ExchangeRateDialog from 'containers/Dialogs/ExchangeRateDialog';
|
// import ExchangeRateDialog from 'containers/Dialogs/ExchangeRateDialog';
|
||||||
|
|
||||||
|
|
||||||
export default function DialogsContainer() {
|
export default function DialogsContainer() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ExchangeRateDialog />
|
<AccountFormDialog dialogName={'account-form'} />
|
||||||
{/* <InviteUserDialog /> */}
|
|
||||||
<CurrencyDialog />
|
|
||||||
<ItemCategoryDialog />
|
|
||||||
<AccountFormDialog />
|
|
||||||
{/* <UserFormDialog /> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export const BooleanCompatators = [
|
export const BooleanCompatators = [
|
||||||
{ value: 'is', label_id: 'is' },
|
{ value: 'is', label_id: 'is' },
|
||||||
|
{ value: 'is_not', label_id: 'is_not' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const TextCompatators = [
|
export const TextCompatators = [
|
||||||
@@ -20,6 +21,15 @@ export const OptionsCompatators = [
|
|||||||
{ value: 'is_not', label_id: 'is_not' },
|
{ value: 'is_not', label_id: 'is_not' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const NumberCampatators = [
|
||||||
|
{ value: 'equals', label_id: 'equals' },
|
||||||
|
{ value: 'not_equal', label_id: 'not_equal' },
|
||||||
|
{ value: 'bigger_than', label_id: 'bigger_than' },
|
||||||
|
{ value: 'bigger_or_equals', label_id: 'bigger_or_equals' },
|
||||||
|
{ value: 'smaller_than', label_id: 'smaller_than' },
|
||||||
|
{ value: 'smaller_or_equals', label_id: 'smaller_or_equals' },
|
||||||
|
]
|
||||||
|
|
||||||
export const getConditionTypeCompatators = (dataType) => {
|
export const getConditionTypeCompatators = (dataType) => {
|
||||||
return [
|
return [
|
||||||
...(dataType === 'options'
|
...(dataType === 'options'
|
||||||
@@ -28,6 +38,8 @@ export const getConditionTypeCompatators = (dataType) => {
|
|||||||
? [...DateCompatators]
|
? [...DateCompatators]
|
||||||
: dataType === 'boolean'
|
: dataType === 'boolean'
|
||||||
? [...BooleanCompatators]
|
? [...BooleanCompatators]
|
||||||
|
: dataType === 'number'
|
||||||
|
? [...NumberCampatators]
|
||||||
: [...TextCompatators]),
|
: [...TextCompatators]),
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,72 +14,80 @@ import { FormattedMessage as T, useIntl } from 'react-intl';
|
|||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import { If, Choose, ListSelect, MODIFIER } from 'components';
|
import { Choose, ListSelect, MODIFIER } from 'components';
|
||||||
|
|
||||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
||||||
import withResourceActions from 'containers/Resources/withResourcesActions';
|
import withResourceActions from 'containers/Resources/withResourcesActions';
|
||||||
|
|
||||||
import {
|
|
||||||
getConditionTypeCompatators,
|
|
||||||
getConditionDefaultCompatator,
|
|
||||||
} from './DynamicFilterCompatators';
|
|
||||||
|
|
||||||
import { compose, momentFormatter } from 'utils';
|
import { compose, momentFormatter } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamic filter fields.
|
* Dynamic filter fields.
|
||||||
*/
|
*/
|
||||||
function DynamicFilterValueField({
|
function DynamicFilterValueField({
|
||||||
dataType,
|
|
||||||
value,
|
|
||||||
|
|
||||||
initialValue,
|
|
||||||
error,
|
|
||||||
// fieldkey,
|
|
||||||
// resourceKey,
|
|
||||||
|
|
||||||
// #withResourceDetail
|
// #withResourceDetail
|
||||||
resourceName,
|
resourceName,
|
||||||
resourceData,
|
resourceData = [],
|
||||||
|
|
||||||
requestResourceData,
|
requestResourceData,
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
|
fieldType,
|
||||||
|
fieldName,
|
||||||
|
value,
|
||||||
|
initialValue,
|
||||||
|
error,
|
||||||
|
optionsResource,
|
||||||
|
optionsKey = 'key',
|
||||||
|
optionsLabel = 'label',
|
||||||
|
options,
|
||||||
onChange,
|
onChange,
|
||||||
rosourceKey,
|
inputDebounceWait = 250,
|
||||||
|
|
||||||
inputDebounceWait = 500,
|
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [localValue, setLocalValue] = useState();
|
const [localValue, setLocalValue] = useState();
|
||||||
|
|
||||||
const fetchResourceData = useQuery(
|
// Makes `localValue` controlled mode from `value`.
|
||||||
['resource-data', resourceName && resourceName],
|
|
||||||
(k, resName) => requestResourceData(resName),
|
|
||||||
{ manual: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value !== localValue) {
|
if (value !== localValue) {
|
||||||
setLocalValue(value);
|
setLocalValue(value);
|
||||||
}
|
}
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
|
// Fetches resource data.
|
||||||
|
const fetchResourceData = useQuery(
|
||||||
|
['resource-data', resourceName],
|
||||||
|
(key, _resourceName) => requestResourceData(_resourceName),
|
||||||
|
{
|
||||||
|
enabled: resourceName,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Account type item of select filed.
|
// Account type item of select filed.
|
||||||
const menuItem = (item, { handleClick, modifiers, query }) => {
|
const menuItem = (item, { handleClick, modifiers, query }) => {
|
||||||
return <MenuItem text={item.name} key={item.id} onClick={handleClick} />;
|
return (<MenuItem
|
||||||
|
text={item[optionsLabel]}
|
||||||
|
key={item[optionsKey]}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle list button click.
|
||||||
const handleBtnClick = () => {
|
const handleBtnClick = () => {
|
||||||
fetchResourceData.refetch({});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const listOptions = useMemo(() => Object.values(resourceData), [
|
const listOptions = useMemo(() => [
|
||||||
resourceData,
|
...(resourceData || []),
|
||||||
|
...(options || []),
|
||||||
|
], [
|
||||||
|
resourceData, options,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Filters accounts types items.
|
// Filters accounts types items.
|
||||||
const filterItems = (query, item, _index, exactMatch) => {
|
const filterItems = (query, item, _index, exactMatch) => {
|
||||||
const normalizedTitle = item.name.toLowerCase();
|
const normalizedTitle = item.label.toLowerCase();
|
||||||
const normalizedQuery = query.toLowerCase();
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
|
||||||
if (exactMatch) {
|
if (exactMatch) {
|
||||||
@@ -89,16 +97,16 @@ function DynamicFilterValueField({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle list item selected.
|
||||||
const onItemSelect = (item) => {
|
const onItemSelect = (item) => {
|
||||||
onChange && onChange(item);
|
onChange && onChange(item[optionsKey]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInputChangeThrottled = useRef(
|
const handleInputChangeThrottled = useRef(
|
||||||
debounce((value) => {
|
debounce((value) => { onChange && onChange(value); }, inputDebounceWait),
|
||||||
onChange && onChange(value);
|
|
||||||
}, inputDebounceWait),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Handle input change.
|
||||||
const handleInputChange = (e) => {
|
const handleInputChange = (e) => {
|
||||||
if (e.currentTarget.type === 'checkbox') {
|
if (e.currentTarget.type === 'checkbox') {
|
||||||
setLocalValue(e.currentTarget.checked);
|
setLocalValue(e.currentTarget.checked);
|
||||||
@@ -108,12 +116,14 @@ function DynamicFilterValueField({
|
|||||||
handleInputChangeThrottled.current(e.currentTarget.value);
|
handleInputChangeThrottled.current(e.currentTarget.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle checkbox field change.
|
||||||
const handleCheckboxChange = (e) => {
|
const handleCheckboxChange = (e) => {
|
||||||
const value = !!e.currentTarget.checked;
|
const value = !!e.currentTarget.checked;
|
||||||
setLocalValue(value);
|
setLocalValue(value);
|
||||||
onChange && onChange(value);
|
onChange && onChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle date field change.
|
||||||
const handleDateChange = (date) => {
|
const handleDateChange = (date) => {
|
||||||
setLocalValue(date);
|
setLocalValue(date);
|
||||||
onChange && onChange(date);
|
onChange && onChange(date);
|
||||||
@@ -126,7 +136,7 @@ function DynamicFilterValueField({
|
|||||||
return (
|
return (
|
||||||
<FormGroup className={'form-group--value'}>
|
<FormGroup className={'form-group--value'}>
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={dataType === 'options'}>
|
<Choose.When condition={fieldType === 'options'}>
|
||||||
<ListSelect
|
<ListSelect
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'list-select--filter-dropdown',
|
'list-select--filter-dropdown',
|
||||||
@@ -146,14 +156,16 @@ function DynamicFilterValueField({
|
|||||||
}}
|
}}
|
||||||
onItemSelect={onItemSelect}
|
onItemSelect={onItemSelect}
|
||||||
selectedItem={value}
|
selectedItem={value}
|
||||||
selectedItemProp={'id'}
|
selectedItemProp={optionsKey}
|
||||||
defaultText={<T id={'select_account_type'} />}
|
defaultText={`Select an option`}
|
||||||
labelProp={'name'}
|
labelProp={optionsLabel}
|
||||||
buttonProps={{ onClick: handleBtnClick }}
|
buttonProps={{
|
||||||
|
onClick: handleBtnClick
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.When condition={dataType === 'date'}>
|
<Choose.When condition={fieldType === 'date'}>
|
||||||
<DateInput
|
<DateInput
|
||||||
{...momentFormatter('YYYY/MM/DD')}
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
value={transformDateValue(localValue)}
|
value={transformDateValue(localValue)}
|
||||||
@@ -167,7 +179,7 @@ function DynamicFilterValueField({
|
|||||||
/>
|
/>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.When condition={dataType === 'boolean'}>
|
<Choose.When condition={fieldType === 'checkbox'}>
|
||||||
<Checkbox value={localValue} onChange={handleCheckboxChange} />
|
<Checkbox value={localValue} onChange={handleCheckboxChange} />
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
@@ -184,7 +196,7 @@ function DynamicFilterValueField({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
resourceName: props.dataResource,
|
resourceName: props.optionsResource,
|
||||||
});
|
});
|
||||||
|
|
||||||
const withResourceFilterValueField = connect(mapStateToProps);
|
const withResourceFilterValueField = connect(mapStateToProps);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React, { useEffect, useMemo, useCallback, useRef } from 'react';
|
import React, { useEffect, useMemo, useCallback, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Classes,
|
Classes,
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { isEqual, last } from 'lodash';
|
import { isEqual, last } from 'lodash';
|
||||||
import { usePrevious } from 'react-use';
|
import { usePrevious } from 'react-use';
|
||||||
import { debounce } from 'lodash';
|
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import { checkRequiredProperties, uniqueMultiProps } from 'utils';
|
import { checkRequiredProperties, uniqueMultiProps } from 'utils';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
@@ -22,7 +21,7 @@ import Toaster from 'components/AppToaster';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {
|
import {
|
||||||
getConditionTypeCompatators,
|
getConditionTypeCompatators,
|
||||||
getConditionDefaultCompatator
|
getConditionDefaultCompatator,
|
||||||
} from './DynamicFilter/DynamicFilterCompatators';
|
} from './DynamicFilter/DynamicFilterCompatators';
|
||||||
|
|
||||||
let limitToast;
|
let limitToast;
|
||||||
@@ -39,224 +38,238 @@ type InitialCondition = {
|
|||||||
export default function FilterDropdown({
|
export default function FilterDropdown({
|
||||||
fields,
|
fields,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
refetchDebounceWait = 10,
|
|
||||||
initialCondition,
|
initialCondition,
|
||||||
|
initialConditions,
|
||||||
}) {
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
// Fields key -> metadata table.
|
||||||
|
const fieldsKeyMapped = useMemo(() =>
|
||||||
|
new Map(fields.map((field) => [field.key, field])),
|
||||||
|
[fields]
|
||||||
|
);
|
||||||
|
// Conditions options.
|
||||||
|
const conditionalsOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{ value: '&&', label: formatMessage({ id: 'and' }) },
|
||||||
|
{ value: '||', label: formatMessage({ id: 'or' }) },
|
||||||
|
],
|
||||||
|
[formatMessage],
|
||||||
|
);
|
||||||
|
// Resources fileds options for fields options.
|
||||||
|
const resourceFieldsOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
...fields.map((field) => ({
|
||||||
|
value: field.key,
|
||||||
|
label: field.label,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
[fields],
|
||||||
|
);
|
||||||
|
// Default filter conition.
|
||||||
|
const defaultFilterCondition = useMemo(
|
||||||
|
() => ({
|
||||||
|
condition: '&&',
|
||||||
|
fieldKey: initialCondition.fieldKey,
|
||||||
|
comparator: initialCondition.comparator,
|
||||||
|
value: initialCondition.value,
|
||||||
|
}),
|
||||||
|
[initialCondition],
|
||||||
|
);
|
||||||
|
// Formik for validation purposes.
|
||||||
|
const { setFieldValue, getFieldProps, values } = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
conditions: [
|
||||||
|
...((initialConditions && initialConditions.length) ?
|
||||||
|
[...initialConditions] : [defaultFilterCondition]),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle click a new filter row.
|
||||||
|
const onClickNewFilter = useCallback(() => {
|
||||||
|
if (values.conditions.length >= 12) {
|
||||||
|
limitToast = Toaster.show(
|
||||||
|
{
|
||||||
|
message: formatMessage({ id: 'you_reached_conditions_limit' }),
|
||||||
|
intent: Intent.WARNING,
|
||||||
|
},
|
||||||
|
limitToast,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setFieldValue('conditions', [
|
||||||
|
...values.conditions,
|
||||||
|
defaultFilterCondition
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}, [values, setFieldValue, formatMessage, defaultFilterCondition]);
|
||||||
|
|
||||||
|
// Filtered conditions that filters conditions that don't contain atleast
|
||||||
|
// on required fields or fileds keys that not exists.
|
||||||
|
const filteredFilterConditions = useMemo(() => {
|
||||||
|
const requiredProps = ['fieldKey', 'condition', 'comparator', 'value'];
|
||||||
|
|
||||||
|
const conditions = values.conditions
|
||||||
|
.filter(
|
||||||
|
(condition) => !checkRequiredProperties(condition, requiredProps),
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
(condition) => !!fieldsKeyMapped.get(condition.fieldKey),
|
||||||
|
);
|
||||||
|
return uniqueMultiProps(conditions, requiredProps);
|
||||||
|
}, [values.conditions, fieldsKeyMapped]);
|
||||||
|
|
||||||
|
// Previous filtered conditions.
|
||||||
|
const prevConditions = usePrevious(filteredFilterConditions);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Campare the current conditions with previous conditions, if they were equal
|
||||||
|
// there is no need to execute `onFilterChange` function.
|
||||||
|
if (!isEqual(prevConditions, filteredFilterConditions) && prevConditions) {
|
||||||
|
onFilterChange && onFilterChange(filteredFilterConditions);
|
||||||
|
}
|
||||||
|
}, [filteredFilterConditions, prevConditions, onFilterChange]);
|
||||||
|
|
||||||
|
// Handle click remove condition.
|
||||||
|
const onClickRemoveCondition = (index) => () => {
|
||||||
|
if (values.conditions.length === 1) {
|
||||||
|
setFieldValue('conditions', [defaultFilterCondition]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const conditions = [...values.conditions];
|
||||||
|
conditions.splice(index, 1);
|
||||||
|
setFieldValue('conditions', [...conditions]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Transform dynamic value field.
|
||||||
|
const transformValueField = (value) => {
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return moment(value).format('YYYY-MM-DD');
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
return value.id;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
// Override getFieldProps for conditions fields.
|
||||||
|
const fieldProps = (name, index) => {
|
||||||
|
const override = {
|
||||||
|
...getFieldProps(`conditions[${index}].${name}`),
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...override,
|
||||||
|
onChange: (e) => {
|
||||||
|
if (name === 'fieldKey') {
|
||||||
|
const currentField = fieldsKeyMapped.get(
|
||||||
|
values.conditions[index].fieldKey,
|
||||||
|
);
|
||||||
|
const nextField = fieldsKeyMapped.get(e.currentTarget.value);
|
||||||
|
|
||||||
|
if (currentField.field_type !== nextField.field_type) {
|
||||||
|
setFieldValue(`conditions[${index}].value`, '');
|
||||||
|
}
|
||||||
|
const comparatorsObs = getConditionTypeCompatators(
|
||||||
|
nextField.field_type,
|
||||||
|
);
|
||||||
|
const currentCompatator = values.conditions[index].comparator;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!currentCompatator ||
|
||||||
|
comparatorsObs.map((c) => c.value).indexOf(currentCompatator) === -1
|
||||||
|
) {
|
||||||
|
const defaultCompatator = getConditionDefaultCompatator(
|
||||||
|
nextField.field_type,
|
||||||
|
);
|
||||||
|
setFieldValue(
|
||||||
|
`conditions[${index}].comparator`,
|
||||||
|
defaultCompatator.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override.onChange(e);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compatator field props.
|
||||||
|
const comparatorFieldProps = (name, index) => {
|
||||||
|
const condition = values.conditions[index];
|
||||||
|
const field = fieldsKeyMapped.get(condition.fieldKey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...fieldProps(name, index),
|
||||||
|
dataType: field.field_type,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Value field props.
|
||||||
|
const valueFieldProps = (name, index) => {
|
||||||
|
const condition = values.conditions[index];
|
||||||
|
const field = fieldsKeyMapped.get(condition.fieldKey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...fieldProps(name, index),
|
||||||
|
fieldName: field.label,
|
||||||
|
fieldType: field.field_type,
|
||||||
|
options: field.options,
|
||||||
|
optionsResource: field.options_resource,
|
||||||
|
onChange: (value) => {
|
||||||
|
const transformedValue = transformValueField(value);
|
||||||
|
setFieldValue(`conditions[${index}].${name}`, transformedValue);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="filter-dropdown">
|
<div class="filter-dropdown">
|
||||||
<div class="filter-dropdown__body">
|
<div class="filter-dropdown__body">
|
||||||
|
{values.conditions.map((condition, index) => (
|
||||||
|
<div class="filter-dropdown__condition">
|
||||||
|
<FormGroup className={'form-group--condition'}>
|
||||||
|
<HTMLSelect
|
||||||
|
options={conditionalsOptions}
|
||||||
|
className={Classes.FILL}
|
||||||
|
disabled={index > 1}
|
||||||
|
{...fieldProps('condition', index)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup className={'form-group--field'}>
|
||||||
|
<HTMLSelect
|
||||||
|
options={resourceFieldsOptions}
|
||||||
|
value={1}
|
||||||
|
className={Classes.FILL}
|
||||||
|
{...fieldProps('fieldKey', index)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup className={'form-group--comparator'}>
|
||||||
|
<DynamicFilterCompatatorField
|
||||||
|
className={Classes.FILL}
|
||||||
|
{...comparatorFieldProps('comparator', index)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<DynamicFilterValueField
|
||||||
|
{...valueFieldProps('value', index)} />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
icon={<Icon icon="times" iconSize={14} />}
|
||||||
|
minimal={true}
|
||||||
|
onClick={onClickRemoveCondition(index)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-dropdown__footer">
|
||||||
|
<Button
|
||||||
|
minimal={true}
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
onClick={onClickNewFilter}
|
||||||
|
>
|
||||||
|
<T id={'new_conditional'} />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// const { formatMessage } = useIntl();
|
|
||||||
// const fieldsKeyMapped = new Map(fields.map((field) => [field.key, field]));
|
|
||||||
|
|
||||||
// const conditionalsItems = useMemo(
|
|
||||||
// () => [
|
|
||||||
// { value: 'and', label: formatMessage({ id: 'and' }) },
|
|
||||||
// { value: 'or', label: formatMessage({ id: 'or' }) },
|
|
||||||
// ],
|
|
||||||
// [formatMessage],
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const resourceFields = useMemo(
|
|
||||||
// () => [
|
|
||||||
// ...fields.map((field) => ({
|
|
||||||
// value: field.key,
|
|
||||||
// label: field.label_name,
|
|
||||||
// })),
|
|
||||||
// ],
|
|
||||||
// [fields],
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const defaultFilterCondition = useMemo(
|
|
||||||
// () => ({
|
|
||||||
// condition: 'and',
|
|
||||||
// field_key: initialCondition.fieldKey,
|
|
||||||
// comparator: initialCondition.comparator,
|
|
||||||
// value: initialCondition.value,
|
|
||||||
// }),
|
|
||||||
// [fields],
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const { setFieldValue, getFieldProps, values } = useFormik({
|
|
||||||
// enableReinitialize: true,
|
|
||||||
// initialValues: {
|
|
||||||
// conditions: [defaultFilterCondition],
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const onClickNewFilter = useCallback(() => {
|
|
||||||
// if (values.conditions.length >= 12) {
|
|
||||||
// limitToast = Toaster.show(
|
|
||||||
// {
|
|
||||||
// message: formatMessage({ id: 'you_reached_conditions_limit' }),
|
|
||||||
// intent: Intent.WARNING,
|
|
||||||
// },
|
|
||||||
// limitToast,
|
|
||||||
// );
|
|
||||||
// } else {
|
|
||||||
// setFieldValue('conditions', [
|
|
||||||
// ...values.conditions,
|
|
||||||
// last(values.conditions),
|
|
||||||
// ]);
|
|
||||||
// }
|
|
||||||
// }, [values, defaultFilterCondition, setFieldValue]);
|
|
||||||
|
|
||||||
// const filteredFilterConditions = useMemo(() => {
|
|
||||||
// const requiredProps = ['field_key', 'condition', 'comparator', 'value'];
|
|
||||||
// const conditions = values.conditions.filter(
|
|
||||||
// (condition) => !checkRequiredProperties(condition, requiredProps),
|
|
||||||
// );
|
|
||||||
// return uniqueMultiProps(conditions, requiredProps);
|
|
||||||
// }, [values.conditions]);
|
|
||||||
|
|
||||||
// const prevConditions = usePrevious(filteredFilterConditions);
|
|
||||||
|
|
||||||
// const onFilterChangeThrottled = useRef(
|
|
||||||
// debounce((conditions) => {
|
|
||||||
// onFilterChange && onFilterChange(conditions);
|
|
||||||
// }, refetchDebounceWait),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (!isEqual(prevConditions, filteredFilterConditions) && prevConditions) {
|
|
||||||
// onFilterChange && onFilterChange(filteredFilterConditions);
|
|
||||||
// }
|
|
||||||
// }, [filteredFilterConditions, prevConditions]);
|
|
||||||
|
|
||||||
// // Handle click remove condition.
|
|
||||||
// const onClickRemoveCondition = (index) => () => {
|
|
||||||
// if (values.conditions.length === 1) {
|
|
||||||
// setFieldValue('conditions', [defaultFilterCondition]);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// const conditions = [...values.conditions];
|
|
||||||
// conditions.splice(index, 1);
|
|
||||||
// setFieldValue('conditions', [...conditions]);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Transform dynamic value field.
|
|
||||||
// const transformValueField = (value) => {
|
|
||||||
// if (value instanceof Date) {
|
|
||||||
// return moment(value).format('YYYY-MM-DD');
|
|
||||||
// } else if (typeof value === 'object') {
|
|
||||||
// return value.id;
|
|
||||||
// }
|
|
||||||
// return value;
|
|
||||||
// };
|
|
||||||
// // Override getFieldProps for conditions fields.
|
|
||||||
// const fieldProps = (name, index) => {
|
|
||||||
// const override = {
|
|
||||||
// ...getFieldProps(`conditions[${index}].${name}`),
|
|
||||||
// };
|
|
||||||
// return {
|
|
||||||
// ...override,
|
|
||||||
// onChange: (e) => {
|
|
||||||
// if (name === 'field_key') {
|
|
||||||
// const currentField = fieldsKeyMapped.get(
|
|
||||||
// values.conditions[index].field_key,
|
|
||||||
// );
|
|
||||||
// const nextField = fieldsKeyMapped.get(e.currentTarget.value);
|
|
||||||
|
|
||||||
// if (currentField.data_type !== nextField.data_type) {
|
|
||||||
// setFieldValue(`conditions[${index}].value`, '');
|
|
||||||
// }
|
|
||||||
// const comparatorsObs = getConditionTypeCompatators(nextField.data_type);
|
|
||||||
// const currentCompatator = values.conditions[index].comparator;
|
|
||||||
|
|
||||||
// if (!currentCompatator || comparatorsObs.map(c => c.value).indexOf(currentCompatator) === -1) {
|
|
||||||
// const defaultCompatator = getConditionDefaultCompatator(nextField.data_type);
|
|
||||||
// setFieldValue(`conditions[${index}].comparator`, defaultCompatator.value);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// override.onChange(e);
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Compatator field props.
|
|
||||||
// const comparatorFieldProps = (name, index) => {
|
|
||||||
// const condition = values.conditions[index];
|
|
||||||
// const field = fieldsKeyMapped.get(condition.field_key);
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// ...fieldProps(name, index),
|
|
||||||
// dataType: field.data_type,
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Value field props.
|
|
||||||
// const valueFieldProps = (name, index) => {
|
|
||||||
// const condition = values.conditions[index];
|
|
||||||
// const field = fieldsKeyMapped.get(condition.field_key);
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// ...fieldProps(name, index),
|
|
||||||
// dataType: field.data_type,
|
|
||||||
// resourceKey: field.resource_key,
|
|
||||||
// options: field.options,
|
|
||||||
// dataResource: field.data_resource,
|
|
||||||
// onChange: (value) => {
|
|
||||||
// const transformedValue = transformValueField(value);
|
|
||||||
// setFieldValue(`conditions[${index}].${name}`, transformedValue);
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <div class="filter-dropdown">
|
|
||||||
// <div class="filter-dropdown__body">
|
|
||||||
// {values.conditions.map((condition, index) => (
|
|
||||||
// <div class="filter-dropdown__condition">
|
|
||||||
// <FormGroup className={'form-group--condition'}>
|
|
||||||
// <HTMLSelect
|
|
||||||
// options={conditionalsItems}
|
|
||||||
// className={Classes.FILL}
|
|
||||||
// disabled={index > 1}
|
|
||||||
// {...fieldProps('condition', index)}
|
|
||||||
// />
|
|
||||||
// </FormGroup>
|
|
||||||
|
|
||||||
// <FormGroup className={'form-group--field'}>
|
|
||||||
// <HTMLSelect
|
|
||||||
// options={resourceFields}
|
|
||||||
// value={1}
|
|
||||||
// className={Classes.FILL}
|
|
||||||
// {...fieldProps('field_key', index)}
|
|
||||||
// />
|
|
||||||
// </FormGroup>
|
|
||||||
|
|
||||||
// <FormGroup className={'form-group--comparator'}>
|
|
||||||
// <DynamicFilterCompatatorField
|
|
||||||
// className={Classes.FILL}
|
|
||||||
// {...comparatorFieldProps('comparator', index)}
|
|
||||||
// />
|
|
||||||
// </FormGroup>
|
|
||||||
|
|
||||||
// <DynamicFilterValueField {...valueFieldProps('value', index)} />
|
|
||||||
|
|
||||||
// <Button
|
|
||||||
// icon={<Icon icon="times" iconSize={14} />}
|
|
||||||
// minimal={true}
|
|
||||||
// onClick={onClickRemoveCondition(index)}
|
|
||||||
// />
|
|
||||||
// </div>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// <div class="filter-dropdown__footer">
|
|
||||||
// <Button
|
|
||||||
// minimal={true}
|
|
||||||
// intent={Intent.PRIMARY}
|
|
||||||
// onClick={onClickNewFilter}
|
|
||||||
// >
|
|
||||||
// <T id={'new_conditional'} />
|
|
||||||
// </Button>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
|
|||||||
9
client/src/components/Forms/InputPrepend.js
Normal file
9
client/src/components/Forms/InputPrepend.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function InputPrepend({ children }) {
|
||||||
|
return (
|
||||||
|
<div class="input-prepend">
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
0
client/src/components/Forms/InputPrependOptions.js
Normal file
0
client/src/components/Forms/InputPrependOptions.js
Normal file
@@ -5,7 +5,7 @@ const Bar = ({ progress, animationDuration }) => (
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
background: '#79b8ff',
|
background: '#79b8ff',
|
||||||
height: 3,
|
height: 4,
|
||||||
left: 0,
|
left: 0,
|
||||||
marginLeft: `${(-1 + progress) * 100}%`,
|
marginLeft: `${(-1 + progress) * 100}%`,
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
|
|||||||
@@ -15,13 +15,15 @@ import Pagination from './Pagination';
|
|||||||
import DashboardViewsTabs from './Dashboard/DashboardViewsTabs';
|
import DashboardViewsTabs from './Dashboard/DashboardViewsTabs';
|
||||||
import CurrenciesSelectList from './CurrenciesSelectList';
|
import CurrenciesSelectList from './CurrenciesSelectList';
|
||||||
import FieldRequiredHint from './FieldRequiredHint';
|
import FieldRequiredHint from './FieldRequiredHint';
|
||||||
import Dialog from './Dialog';
|
|
||||||
import AppToaster from './AppToaster';
|
import AppToaster from './AppToaster';
|
||||||
import DataTable from './DataTable';
|
import DataTable from './DataTable';
|
||||||
import AccountsSelectList from './AccountsSelectList';
|
import AccountsSelectList from './AccountsSelectList';
|
||||||
import AccountsTypesSelect from './AccountsTypesSelect';
|
import AccountsTypesSelect from './AccountsTypesSelect';
|
||||||
import LoadingIndicator from './LoadingIndicator';
|
import LoadingIndicator from './LoadingIndicator';
|
||||||
import DashboardActionViewsList from './Dashboard/DashboardActionViewsList';
|
import DashboardActionViewsList from './Dashboard/DashboardActionViewsList';
|
||||||
|
import Dialog from './Dialog/Dialog';
|
||||||
|
import DialogContent from './Dialog/DialogContent';
|
||||||
|
import DialogSuspense from './Dialog/DialogSuspense';
|
||||||
const Hint = FieldHint;
|
const Hint = FieldHint;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -43,11 +45,13 @@ export {
|
|||||||
DashboardViewsTabs,
|
DashboardViewsTabs,
|
||||||
CurrenciesSelectList,
|
CurrenciesSelectList,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
Dialog,
|
|
||||||
AppToaster,
|
|
||||||
DataTable,
|
DataTable,
|
||||||
AccountsSelectList,
|
AccountsSelectList,
|
||||||
AccountsTypesSelect,
|
AccountsTypesSelect,
|
||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
DashboardActionViewsList,
|
DashboardActionViewsList,
|
||||||
|
AppToaster,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogSuspense
|
||||||
};
|
};
|
||||||
@@ -35,6 +35,9 @@ function AccountsActionsBar({
|
|||||||
// #withAccountsActions
|
// #withAccountsActions
|
||||||
addAccountsTableQueries,
|
addAccountsTableQueries,
|
||||||
|
|
||||||
|
// #withAccounts
|
||||||
|
accountsTableQuery,
|
||||||
|
|
||||||
selectedRows = [],
|
selectedRows = [],
|
||||||
onFilterChanged,
|
onFilterChanged,
|
||||||
onBulkDelete,
|
onBulkDelete,
|
||||||
@@ -42,7 +45,9 @@ function AccountsActionsBar({
|
|||||||
onBulkActivate,
|
onBulkActivate,
|
||||||
onBulkInactive,
|
onBulkInactive,
|
||||||
}) {
|
}) {
|
||||||
const [filterCount, setFilterCount] = useState(0);
|
const [filterCount, setFilterCount] = useState(
|
||||||
|
accountsTableQuery?.filter_roles?.length || 0,
|
||||||
|
);
|
||||||
|
|
||||||
const onClickNewAccount = () => {
|
const onClickNewAccount = () => {
|
||||||
openDialog('account-form', {});
|
openDialog('account-form', {});
|
||||||
@@ -54,9 +59,10 @@ function AccountsActionsBar({
|
|||||||
|
|
||||||
const filterDropdown = FilterDropdown({
|
const filterDropdown = FilterDropdown({
|
||||||
fields: resourceFields,
|
fields: resourceFields,
|
||||||
|
initialConditions: accountsTableQuery.filter_roles,
|
||||||
initialCondition: {
|
initialCondition: {
|
||||||
fieldKey: 'name',
|
fieldKey: 'name',
|
||||||
compatator: 'contains',
|
comparator: 'contains',
|
||||||
value: '',
|
value: '',
|
||||||
},
|
},
|
||||||
onFilterChange: (filterConditions) => {
|
onFilterChange: (filterConditions) => {
|
||||||
@@ -171,8 +177,9 @@ const withAccountsActionsBar = connect(mapStateToProps);
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAccountsActionsBar,
|
withAccountsActionsBar,
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
withAccounts(({ accountsViews }) => ({
|
withAccounts(({ accountsViews, accountsTableQuery }) => ({
|
||||||
accountsViews,
|
accountsViews,
|
||||||
|
accountsTableQuery,
|
||||||
})),
|
})),
|
||||||
withResourceDetail(({ resourceFields }) => ({
|
withResourceDetail(({ resourceFields }) => ({
|
||||||
resourceFields,
|
resourceFields,
|
||||||
|
|||||||
@@ -51,11 +51,10 @@ function NormalCell({ cell }) {
|
|||||||
|
|
||||||
function BalanceCell({ cell }) {
|
function BalanceCell({ cell }) {
|
||||||
const account = cell.row.original;
|
const account = cell.row.original;
|
||||||
const { balance = null } = account;
|
|
||||||
|
|
||||||
return balance ? (
|
return (account.amount) ? (
|
||||||
<span>
|
<span>
|
||||||
<Money amount={balance.amount} currency={balance.currency_code} />
|
<Money amount={account.amount} currency={'USD'} />
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span class="placeholder">—</span>
|
<span class="placeholder">—</span>
|
||||||
@@ -245,7 +244,7 @@ function AccountsDataTable({
|
|||||||
{
|
{
|
||||||
id: 'balance',
|
id: 'balance',
|
||||||
Header: formatMessage({ id: 'balance' }),
|
Header: formatMessage({ id: 'balance' }),
|
||||||
accessor: 'balance',
|
accessor: 'amount',
|
||||||
Cell: BalanceCell,
|
Cell: BalanceCell,
|
||||||
width: 150,
|
width: 150,
|
||||||
},
|
},
|
||||||
@@ -268,11 +267,7 @@ function AccountsDataTable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const selectionColumn = useMemo(
|
const selectionColumn = useMemo(
|
||||||
() => ({
|
() => ({ minWidth: 45, width: 45, maxWidth: 45 }),
|
||||||
minWidth: 40,
|
|
||||||
width: 40,
|
|
||||||
maxWidth: 40,
|
|
||||||
}),
|
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -32,13 +32,13 @@ function CustomerForm({
|
|||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
|
||||||
//#withCustomers
|
// #withCustomers
|
||||||
customers,
|
customers,
|
||||||
|
|
||||||
//#withCustomerDetail
|
// #withCustomerDetail
|
||||||
customer,
|
customer,
|
||||||
|
|
||||||
//#withCustomersActions
|
// #withCustomersActions
|
||||||
requestSubmitCustomer,
|
requestSubmitCustomer,
|
||||||
requestFetchCustomers,
|
requestFetchCustomers,
|
||||||
requestEditCustomer,
|
requestEditCustomer,
|
||||||
@@ -47,7 +47,7 @@ function CustomerForm({
|
|||||||
requestSubmitMedia,
|
requestSubmitMedia,
|
||||||
requestDeleteMedia,
|
requestDeleteMedia,
|
||||||
|
|
||||||
//#Props
|
// #Props
|
||||||
onFormSubmit,
|
onFormSubmit,
|
||||||
onCancelForm,
|
onCancelForm,
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { RadioGroup, Radio } from '@blueprintjs/core';
|
|||||||
export default function RadioCustomer(props) {
|
export default function RadioCustomer(props) {
|
||||||
const { onChange, ...rest } = props;
|
const { onChange, ...rest } = props;
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
inline={true}
|
inline={true}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import { compose } from 'utils';
|
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
|
||||||
import withDialogRedux from 'components/DialogReduxConnect';
|
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
|
||||||
import withAccountDetail from 'containers/Accounts/withAccountDetail';
|
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
|
||||||
|
|
||||||
export const mapStateToProps = (state, props) => ({
|
|
||||||
dialogName: 'account-form',
|
|
||||||
accountId:
|
|
||||||
props.payload.action === 'edit' && props.payload.id
|
|
||||||
? props.payload.id
|
|
||||||
: null,
|
|
||||||
});
|
|
||||||
const AccountFormDialogConnect = connect(mapStateToProps);
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withDialogRedux(null, 'account-form'),
|
|
||||||
AccountFormDialogConnect,
|
|
||||||
withAccountsActions,
|
|
||||||
withAccountDetail,
|
|
||||||
withAccounts(({ accountsTypes, accountsList }) => ({
|
|
||||||
accountsTypes,
|
|
||||||
accounts: accountsList,
|
|
||||||
})),
|
|
||||||
withDialogActions,
|
|
||||||
);
|
|
||||||
@@ -1,405 +1,45 @@
|
|||||||
import React, { useCallback, useMemo, useEffect } from 'react';
|
import React, { lazy } from 'react';
|
||||||
import {
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
Button,
|
import { Dialog, DialogSuspense } from 'components';
|
||||||
Classes,
|
import withDialogRedux from 'components/DialogReduxConnect';
|
||||||
FormGroup,
|
import { compose } from 'utils';
|
||||||
InputGroup,
|
|
||||||
Intent,
|
const AccountFormDialogContent = lazy(() => import('./AccountFormDialogContent'));
|
||||||
TextArea,
|
|
||||||
Checkbox,
|
|
||||||
Position,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { useFormik } from 'formik';
|
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
|
||||||
import { pick, omit } from 'lodash';
|
|
||||||
import { useQuery, queryCache } from 'react-query';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import Yup from 'services/yup';
|
|
||||||
import {
|
|
||||||
If,
|
|
||||||
ErrorMessage,
|
|
||||||
Dialog,
|
|
||||||
AppToaster,
|
|
||||||
FieldRequiredHint,
|
|
||||||
Hint,
|
|
||||||
AccountsSelectList,
|
|
||||||
AccountsTypesSelect,
|
|
||||||
} from 'components';
|
|
||||||
import AccountFormDialogContainer from 'containers/Dialogs/AccountFormDialog.container';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account form dialog.
|
* Account form dialog.
|
||||||
*/
|
*/
|
||||||
function AccountFormDialog({
|
function AccountFormDialog({
|
||||||
dialogName,
|
dialogName,
|
||||||
payload = { action: 'new', id: null },
|
payload = { action: '', id: null },
|
||||||
isOpen,
|
isOpen,
|
||||||
|
|
||||||
// #withAccounts
|
|
||||||
accountsTypes,
|
|
||||||
accounts,
|
|
||||||
|
|
||||||
// #withAccountDetail
|
|
||||||
account,
|
|
||||||
|
|
||||||
// #withAccountsActions
|
|
||||||
requestFetchAccounts,
|
|
||||||
requestFetchAccountTypes,
|
|
||||||
requestFetchAccount,
|
|
||||||
requestSubmitAccount,
|
|
||||||
requestEditAccount,
|
|
||||||
|
|
||||||
// #withDialog
|
|
||||||
closeDialog,
|
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
const validationSchema = Yup.object().shape({
|
|
||||||
name: Yup.string()
|
|
||||||
.required()
|
|
||||||
.min(3)
|
|
||||||
.max(255)
|
|
||||||
.label(formatMessage({ id: 'account_name_' })),
|
|
||||||
code: Yup.string().digits().min(3).max(6),
|
|
||||||
account_type_id: Yup.number()
|
|
||||||
.required()
|
|
||||||
.label(formatMessage({ id: 'account_type_id' })),
|
|
||||||
description: Yup.string().min(3).max(512).nullable().trim(),
|
|
||||||
parent_account_id: Yup.number().nullable(),
|
|
||||||
});
|
|
||||||
const initialValues = useMemo(
|
|
||||||
() => ({
|
|
||||||
account_type_id: null,
|
|
||||||
name: '',
|
|
||||||
code: '',
|
|
||||||
description: '',
|
|
||||||
}),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
const transformApiErrors = (errors) => {
|
|
||||||
const fields = {};
|
|
||||||
if (errors.find((e) => e.type === 'NOT_UNIQUE_CODE')) {
|
|
||||||
fields.code = formatMessage({ id: 'account_code_is_not_unique' });
|
|
||||||
}
|
|
||||||
return fields;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Formik
|
|
||||||
const {
|
|
||||||
errors,
|
|
||||||
values,
|
|
||||||
touched,
|
|
||||||
setFieldValue,
|
|
||||||
resetForm,
|
|
||||||
handleSubmit,
|
|
||||||
isSubmitting,
|
|
||||||
getFieldProps,
|
|
||||||
} = useFormik({
|
|
||||||
enableReinitialize: true,
|
|
||||||
initialValues: {
|
|
||||||
...initialValues,
|
|
||||||
...(payload.action === 'edit' &&
|
|
||||||
pick(account, Object.keys(initialValues))),
|
|
||||||
},
|
|
||||||
validationSchema,
|
|
||||||
onSubmit: (values, { setSubmitting, setErrors }) => {
|
|
||||||
const form = omit(values, ['subaccount']);
|
|
||||||
const toastAccountName = values.code
|
|
||||||
? `${values.code} - ${values.name}`
|
|
||||||
: values.name;
|
|
||||||
|
|
||||||
const afterSubmit = () => {
|
|
||||||
closeDialog(dialogName);
|
|
||||||
queryCache.invalidateQueries('accounts-table');
|
|
||||||
queryCache.invalidateQueries('accounts-list');
|
|
||||||
};
|
|
||||||
const afterErrors = (errors) => {
|
|
||||||
const errorsTransformed = transformApiErrors(errors);
|
|
||||||
setErrors({ ...errorsTransformed });
|
|
||||||
setSubmitting(false);
|
|
||||||
};
|
|
||||||
if (payload.action === 'edit') {
|
|
||||||
requestEditAccount(payload.id, form)
|
|
||||||
.then((response) => {
|
|
||||||
afterSubmit(response);
|
|
||||||
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage(
|
|
||||||
{ id: 'service_has_been_successful_edited' },
|
|
||||||
{
|
|
||||||
name: toastAccountName,
|
|
||||||
service: formatMessage({ id: 'account' }),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(afterErrors);
|
|
||||||
} else {
|
|
||||||
requestSubmitAccount({ form })
|
|
||||||
.then((response) => {
|
|
||||||
afterSubmit(response);
|
|
||||||
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage(
|
|
||||||
{ id: 'service_has_been_successful_created' },
|
|
||||||
{
|
|
||||||
name: toastAccountName,
|
|
||||||
service: formatMessage({ id: 'account' }),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
position: Position.BOTTOM,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(afterErrors);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (values.parent_account_id) {
|
|
||||||
setFieldValue('subaccount', true);
|
|
||||||
}
|
|
||||||
}, [values.parent_account_id]);
|
|
||||||
|
|
||||||
// Reset `parent account id` after change `account type`.
|
|
||||||
useEffect(() => {
|
|
||||||
setFieldValue('parent_account_id', null);
|
|
||||||
}, [values.account_type_id]);
|
|
||||||
|
|
||||||
// Filtered accounts based on the given account type.
|
|
||||||
const filteredAccounts = useMemo(
|
|
||||||
() =>
|
|
||||||
accounts.filter(
|
|
||||||
(account) =>
|
|
||||||
account.account_type_id === values.account_type_id ||
|
|
||||||
!values.account_type_id,
|
|
||||||
),
|
|
||||||
[accounts, values.account_type_id],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handles dialog close.
|
|
||||||
const handleClose = useCallback(() => {
|
|
||||||
closeDialog(dialogName);
|
|
||||||
}, [closeDialog, dialogName]);
|
|
||||||
|
|
||||||
// Fetches accounts list.
|
|
||||||
const fetchAccountsList = useQuery(
|
|
||||||
'accounts-list',
|
|
||||||
() => requestFetchAccounts(),
|
|
||||||
{ enabled: false },
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fetches accounts types.
|
|
||||||
const fetchAccountsTypes = useQuery(
|
|
||||||
'accounts-types-list',
|
|
||||||
async () => {
|
|
||||||
await requestFetchAccountTypes();
|
|
||||||
},
|
|
||||||
{ enabled: false },
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch the given account id on edit mode.
|
|
||||||
const fetchAccount = useQuery(
|
|
||||||
['account', payload.id],
|
|
||||||
(key, _id) => requestFetchAccount(_id),
|
|
||||||
{ enabled: false },
|
|
||||||
);
|
|
||||||
|
|
||||||
const isFetching =
|
|
||||||
fetchAccountsList.isFetching ||
|
|
||||||
fetchAccountsTypes.isFetching ||
|
|
||||||
fetchAccount.isFetching;
|
|
||||||
|
|
||||||
// Fetch requests on dialog opening.
|
|
||||||
const onDialogOpening = useCallback(() => {
|
|
||||||
fetchAccountsList.refetch();
|
|
||||||
fetchAccountsTypes.refetch();
|
|
||||||
|
|
||||||
if (payload.action === 'edit' && payload.id) {
|
|
||||||
fetchAccount.refetch();
|
|
||||||
}
|
|
||||||
if (payload.action === 'new_child') {
|
|
||||||
setFieldValue('parent_account_id', payload.parentAccountId);
|
|
||||||
setFieldValue('account_type_id', payload.accountTypeId);
|
|
||||||
}
|
|
||||||
}, [payload, fetchAccount, fetchAccountsList, fetchAccountsTypes]);
|
|
||||||
|
|
||||||
// Handle account type change.
|
|
||||||
const onChangeAccountType = useCallback(
|
|
||||||
(accountType) => {
|
|
||||||
setFieldValue('account_type_id', accountType.id);
|
|
||||||
},
|
|
||||||
[setFieldValue],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handles change sub-account.
|
|
||||||
const onChangeSubaccount = useCallback(
|
|
||||||
(account) => {
|
|
||||||
setFieldValue('parent_account_id', account.id);
|
|
||||||
},
|
|
||||||
[setFieldValue],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle dialog on closed.
|
|
||||||
const onDialogClosed = useCallback(() => {
|
|
||||||
resetForm();
|
|
||||||
}, [resetForm]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
name={dialogName}
|
name={dialogName}
|
||||||
title={
|
title={
|
||||||
payload.action === 'edit' ? (
|
(payload.action === 'edit') ?
|
||||||
<T id={'edit_account'} />
|
(<T id={'edit_account'} />) :
|
||||||
) : (
|
(<T id={'new_account'} />)
|
||||||
<T id={'new_account'} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
className={{
|
className={'dialog--account-form'}
|
||||||
'dialog--loading': isFetching,
|
|
||||||
'dialog--account-form': true,
|
|
||||||
}}
|
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
canEscapeKeyClose={true}
|
canEscapeKeyClose={true}
|
||||||
onClosed={onDialogClosed}
|
|
||||||
onOpening={onDialogOpening}
|
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
isLoading={isFetching}
|
|
||||||
onClose={handleClose}
|
|
||||||
>
|
>
|
||||||
<form onSubmit={handleSubmit}>
|
<DialogSuspense>
|
||||||
<div className={Classes.DIALOG_BODY}>
|
<AccountFormDialogContent
|
||||||
<FormGroup
|
dialogName={dialogName}
|
||||||
label={<T id={'account_type'} />}
|
accountId={payload.id}
|
||||||
labelInfo={<FieldRequiredHint />}
|
action={payload.action}
|
||||||
className={classNames(
|
parentAccountId={payload.parentAccountId}
|
||||||
'form-group--account-type',
|
accountTypeId={payload.accountTypeId}
|
||||||
'form-group--select-list',
|
/>
|
||||||
Classes.FILL,
|
</DialogSuspense>
|
||||||
)}
|
|
||||||
inline={true}
|
|
||||||
helperText={
|
|
||||||
<ErrorMessage name="account_type_id" {...{ errors, touched }} />
|
|
||||||
}
|
|
||||||
intent={
|
|
||||||
errors.account_type_id && touched.account_type_id && Intent.DANGER
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<AccountsTypesSelect
|
|
||||||
accountsTypes={accountsTypes}
|
|
||||||
selectedTypeId={values.account_type_id}
|
|
||||||
defaultSelectText={<T id={'select_account_type'} />}
|
|
||||||
onTypeSelected={onChangeAccountType}
|
|
||||||
buttonProps={{ disabled: payload.action === 'edit' }}
|
|
||||||
popoverProps={{ minimal: true }}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'account_name'} />}
|
|
||||||
labelInfo={<FieldRequiredHint />}
|
|
||||||
className={'form-group--account-name'}
|
|
||||||
intent={errors.name && touched.name && Intent.DANGER}
|
|
||||||
helperText={<ErrorMessage name="name" {...{ errors, touched }} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<InputGroup
|
|
||||||
medium={true}
|
|
||||||
intent={errors.name && touched.name && Intent.DANGER}
|
|
||||||
{...getFieldProps('name')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'account_code'} />}
|
|
||||||
className={'form-group--account-code'}
|
|
||||||
intent={errors.code && touched.code && Intent.DANGER}
|
|
||||||
helperText={<ErrorMessage name="code" {...{ errors, touched }} />}
|
|
||||||
inline={true}
|
|
||||||
labelInfo={<Hint content={<T id="account_code_hint" />} />}
|
|
||||||
>
|
|
||||||
<InputGroup
|
|
||||||
medium={true}
|
|
||||||
intent={errors.code && touched.code && Intent.DANGER}
|
|
||||||
{...getFieldProps('code')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
label={' '}
|
|
||||||
className={classNames('form-group--subaccount')}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
inline={true}
|
|
||||||
label={
|
|
||||||
<>
|
|
||||||
<T id={'sub_account'} />
|
|
||||||
<Hint />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
{...getFieldProps('subaccount')}
|
|
||||||
checked={values.subaccount}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<If condition={values.subaccount}>
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'parent_account'} />}
|
|
||||||
className={classNames(
|
|
||||||
'form-group--parent-account',
|
|
||||||
'form-group--select-list',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<AccountsSelectList
|
|
||||||
accounts={filteredAccounts}
|
|
||||||
onAccountSelected={onChangeSubaccount}
|
|
||||||
defaultSelectText={<T id={'select_parent_account'} />}
|
|
||||||
selectedAccountId={values.parent_account_id}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'description'} />}
|
|
||||||
className={'form-group--description'}
|
|
||||||
intent={errors.description && Intent.DANGER}
|
|
||||||
helperText={errors.description && errors.credential}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<TextArea
|
|
||||||
growVertically={true}
|
|
||||||
height={280}
|
|
||||||
{...getFieldProps('description')}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
|
||||||
<Button onClick={handleClose} style={{ minWidth: '75px' }}>
|
|
||||||
<T id={'close'} />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
intent={Intent.PRIMARY}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
style={{ minWidth: '75px' }}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
{payload.action === 'edit' ? (
|
|
||||||
<T id={'edit'} />
|
|
||||||
) : (
|
|
||||||
<T id={'submit'} />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AccountFormDialogContainer(AccountFormDialog);
|
export default compose(
|
||||||
|
withDialogRedux(),
|
||||||
|
)(AccountFormDialog);
|
||||||
400
client/src/containers/Dialogs/AccountFormDialogContent.js
Normal file
400
client/src/containers/Dialogs/AccountFormDialogContent.js
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
import React, { useCallback, useMemo, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Classes,
|
||||||
|
FormGroup,
|
||||||
|
InputGroup,
|
||||||
|
Intent,
|
||||||
|
TextArea,
|
||||||
|
Checkbox,
|
||||||
|
Position,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import { pick, omit } from 'lodash';
|
||||||
|
import { useQuery, queryCache } from 'react-query';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import Yup from 'services/yup';
|
||||||
|
import {
|
||||||
|
If,
|
||||||
|
ErrorMessage,
|
||||||
|
AppToaster,
|
||||||
|
FieldRequiredHint,
|
||||||
|
Hint,
|
||||||
|
AccountsSelectList,
|
||||||
|
AccountsTypesSelect,
|
||||||
|
DialogContent,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
|
import withAccountDetail from 'containers/Accounts/withAccountDetail';
|
||||||
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account form dialog content.
|
||||||
|
*/
|
||||||
|
function AccountFormDialogContent({
|
||||||
|
// #withAccounts
|
||||||
|
accountsTypes,
|
||||||
|
accounts,
|
||||||
|
|
||||||
|
// #withAccountDetail
|
||||||
|
account,
|
||||||
|
|
||||||
|
// #withAccountsActions
|
||||||
|
requestFetchAccounts,
|
||||||
|
requestFetchAccountTypes,
|
||||||
|
requestFetchAccount,
|
||||||
|
requestSubmitAccount,
|
||||||
|
requestEditAccount,
|
||||||
|
|
||||||
|
closeDialog,
|
||||||
|
|
||||||
|
// #ownProp
|
||||||
|
accountId,
|
||||||
|
action,
|
||||||
|
dialogName,
|
||||||
|
parentAccountId,
|
||||||
|
accountTypeId,
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const validationSchema = Yup.object().shape({
|
||||||
|
name: Yup.string()
|
||||||
|
.required()
|
||||||
|
.min(3)
|
||||||
|
.max(255)
|
||||||
|
.label(formatMessage({ id: 'account_name_' })),
|
||||||
|
code: Yup.string().digits().min(3).max(6),
|
||||||
|
account_type_id: Yup.number()
|
||||||
|
.required()
|
||||||
|
.label(formatMessage({ id: 'account_type_id' })),
|
||||||
|
description: Yup.string().min(3).max(512).nullable().trim(),
|
||||||
|
parent_account_id: Yup.number().nullable(),
|
||||||
|
});
|
||||||
|
const initialValues = useMemo(
|
||||||
|
() => ({
|
||||||
|
account_type_id: null,
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
description: '',
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const transformApiErrors = (errors) => {
|
||||||
|
const fields = {};
|
||||||
|
if (errors.find((e) => e.type === 'NOT_UNIQUE_CODE')) {
|
||||||
|
fields.code = formatMessage({ id: 'account_code_is_not_unique' });
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Formik
|
||||||
|
const {
|
||||||
|
errors,
|
||||||
|
values,
|
||||||
|
touched,
|
||||||
|
setFieldValue,
|
||||||
|
resetForm,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
getFieldProps,
|
||||||
|
} = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
|
initialValues: {
|
||||||
|
...initialValues,
|
||||||
|
...(accountId && pick(account, Object.keys(initialValues))),
|
||||||
|
},
|
||||||
|
validationSchema,
|
||||||
|
onSubmit: (values, { setSubmitting, setErrors }) => {
|
||||||
|
const form = omit(values, ['subaccount']);
|
||||||
|
const toastAccountName = values.code
|
||||||
|
? `${values.code} - ${values.name}`
|
||||||
|
: values.name;
|
||||||
|
|
||||||
|
const afterSubmit = () => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
queryCache.invalidateQueries('accounts-table');
|
||||||
|
queryCache.invalidateQueries('accounts-list');
|
||||||
|
};
|
||||||
|
const afterErrors = (errors) => {
|
||||||
|
const errorsTransformed = transformApiErrors(errors);
|
||||||
|
setErrors({ ...errorsTransformed });
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
if (accountId) {
|
||||||
|
requestEditAccount(accountId, form)
|
||||||
|
.then((response) => {
|
||||||
|
afterSubmit(response);
|
||||||
|
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage(
|
||||||
|
{ id: 'service_has_been_successful_edited' },
|
||||||
|
{
|
||||||
|
name: toastAccountName,
|
||||||
|
service: formatMessage({ id: 'account' }),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(afterErrors);
|
||||||
|
} else {
|
||||||
|
requestSubmitAccount({ form })
|
||||||
|
.then((response) => {
|
||||||
|
afterSubmit(response);
|
||||||
|
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage(
|
||||||
|
{ id: 'service_has_been_successful_created' },
|
||||||
|
{
|
||||||
|
name: toastAccountName,
|
||||||
|
service: formatMessage({ id: 'account' }),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
position: Position.BOTTOM,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(afterErrors);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (values.parent_account_id) {
|
||||||
|
setFieldValue('subaccount', true);
|
||||||
|
}
|
||||||
|
}, [values.parent_account_id]);
|
||||||
|
|
||||||
|
// Reset `parent account id` after change `account type`.
|
||||||
|
useEffect(() => {
|
||||||
|
setFieldValue('parent_account_id', null);
|
||||||
|
}, [values.account_type_id]);
|
||||||
|
|
||||||
|
// Filtered accounts based on the given account type.
|
||||||
|
const filteredAccounts = useMemo(
|
||||||
|
() =>
|
||||||
|
accounts.filter(
|
||||||
|
(account) =>
|
||||||
|
account.account_type_id === values.account_type_id ||
|
||||||
|
!values.account_type_id,
|
||||||
|
),
|
||||||
|
[accounts, values.account_type_id],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handles dialog close.
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
}, [closeDialog, dialogName]);
|
||||||
|
|
||||||
|
// Fetches accounts list.
|
||||||
|
const fetchAccountsList = useQuery(
|
||||||
|
'accounts-list',
|
||||||
|
() => requestFetchAccounts(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetches accounts types.
|
||||||
|
const fetchAccountsTypes = useQuery(
|
||||||
|
'accounts-types-list',
|
||||||
|
async () => {
|
||||||
|
await requestFetchAccountTypes();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetch the given account id on edit mode.
|
||||||
|
const fetchAccount = useQuery(
|
||||||
|
['account', accountId],
|
||||||
|
(key, _id) => requestFetchAccount(_id),
|
||||||
|
{ enabled: accountId },
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFetching =
|
||||||
|
fetchAccountsList.isFetching ||
|
||||||
|
fetchAccountsTypes.isFetching ||
|
||||||
|
fetchAccount.isFetching;
|
||||||
|
|
||||||
|
// Fetch requests on dialog opening.
|
||||||
|
const onDialogOpening = useCallback(() => {
|
||||||
|
fetchAccountsList.refetch();
|
||||||
|
fetchAccountsTypes.refetch();
|
||||||
|
|
||||||
|
if (action === 'edit' && accountId) {
|
||||||
|
fetchAccount.refetch();
|
||||||
|
}
|
||||||
|
if (action === 'new_child') {
|
||||||
|
setFieldValue('parent_account_id', parentAccountId);
|
||||||
|
setFieldValue('account_type_id', accountTypeId);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
parentAccountId,
|
||||||
|
accountTypeId,
|
||||||
|
fetchAccount,
|
||||||
|
fetchAccountsList,
|
||||||
|
fetchAccountsTypes,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Handle account type change.
|
||||||
|
const onChangeAccountType = useCallback(
|
||||||
|
(accountType) => {
|
||||||
|
setFieldValue('account_type_id', accountType.id);
|
||||||
|
},
|
||||||
|
[setFieldValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handles change sub-account.
|
||||||
|
const onChangeSubaccount = useCallback(
|
||||||
|
(account) => {
|
||||||
|
setFieldValue('parent_account_id', account.id);
|
||||||
|
},
|
||||||
|
[setFieldValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle dialog on closed.
|
||||||
|
const onDialogClosed = useCallback(() => {
|
||||||
|
resetForm();
|
||||||
|
}, [resetForm]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent isLoading={isFetching}>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'account_type'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
className={classNames(
|
||||||
|
'form-group--account-type',
|
||||||
|
'form-group--select-list',
|
||||||
|
Classes.FILL,
|
||||||
|
)}
|
||||||
|
inline={true}
|
||||||
|
helperText={
|
||||||
|
<ErrorMessage name="account_type_id" {...{ errors, touched }} />
|
||||||
|
}
|
||||||
|
intent={
|
||||||
|
errors.account_type_id && touched.account_type_id && Intent.DANGER
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AccountsTypesSelect
|
||||||
|
accountsTypes={accountsTypes}
|
||||||
|
selectedTypeId={values.account_type_id}
|
||||||
|
defaultSelectText={<T id={'select_account_type'} />}
|
||||||
|
onTypeSelected={onChangeAccountType}
|
||||||
|
buttonProps={{ disabled: action === 'edit' }}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'account_name'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
className={'form-group--account-name'}
|
||||||
|
intent={errors.name && touched.name && Intent.DANGER}
|
||||||
|
helperText={<ErrorMessage name="name" {...{ errors, touched }} />}
|
||||||
|
inline={true}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
medium={true}
|
||||||
|
intent={errors.name && touched.name && Intent.DANGER}
|
||||||
|
{...getFieldProps('name')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'account_code'} />}
|
||||||
|
className={'form-group--account-code'}
|
||||||
|
intent={errors.code && touched.code && Intent.DANGER}
|
||||||
|
helperText={<ErrorMessage name="code" {...{ errors, touched }} />}
|
||||||
|
inline={true}
|
||||||
|
labelInfo={<Hint content={<T id="account_code_hint" />} />}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
medium={true}
|
||||||
|
intent={errors.code && touched.code && Intent.DANGER}
|
||||||
|
{...getFieldProps('code')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
label={' '}
|
||||||
|
className={classNames('form-group--subaccount')}
|
||||||
|
inline={true}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
inline={true}
|
||||||
|
label={
|
||||||
|
<>
|
||||||
|
<T id={'sub_account'} />
|
||||||
|
<Hint />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{...getFieldProps('subaccount')}
|
||||||
|
checked={values.subaccount}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<If condition={values.subaccount}>
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'parent_account'} />}
|
||||||
|
className={classNames(
|
||||||
|
'form-group--parent-account',
|
||||||
|
'form-group--select-list',
|
||||||
|
Classes.FILL,
|
||||||
|
)}
|
||||||
|
inline={true}
|
||||||
|
>
|
||||||
|
<AccountsSelectList
|
||||||
|
accounts={filteredAccounts}
|
||||||
|
onAccountSelected={onChangeSubaccount}
|
||||||
|
defaultSelectText={<T id={'select_parent_account'} />}
|
||||||
|
selectedAccountId={values.parent_account_id}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</If>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
className={'form-group--description'}
|
||||||
|
intent={errors.description && Intent.DANGER}
|
||||||
|
helperText={errors.description && errors.credential}
|
||||||
|
inline={true}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
height={280}
|
||||||
|
{...getFieldProps('description')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
<Button onClick={handleClose} style={{ minWidth: '75px' }}>
|
||||||
|
<T id={'close'} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{action === 'edit' ? <T id={'edit'} /> : <T id={'submit'} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAccountsActions,
|
||||||
|
withAccountDetail,
|
||||||
|
withAccounts(({ accountsTypes, accountsList }) => ({
|
||||||
|
accountsTypes,
|
||||||
|
accounts: accountsList,
|
||||||
|
})),
|
||||||
|
withDialogActions,
|
||||||
|
)(AccountFormDialogContent);
|
||||||
@@ -1,20 +1,26 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import t from 'store/types';
|
|
||||||
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function DashboardHomepage({ changePageTitle, name }) {
|
||||||
|
|
||||||
const DashboardHomepage = ({ changePageTitle }) => {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle('Craig’s Design and Landscaping Services')
|
changePageTitle(name)
|
||||||
});
|
}, [name, changePageTitle]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>asdasd</div>
|
<DashboardInsider name="homepage">
|
||||||
|
|
||||||
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapActionsToProps = (dispatch) => ({
|
export default compose(
|
||||||
changePageTitle: pageTitle => dispatch({
|
withDashboardActions,
|
||||||
type: t.CHANGE_DASHBOARD_PAGE_TITLE, pageTitle
|
withSettings(({ organizationSettings }) => ({
|
||||||
}),
|
name: organizationSettings.name,
|
||||||
});
|
})),
|
||||||
|
)(DashboardHomepage);
|
||||||
export default connect(null, mapActionsToProps)(DashboardHomepage);
|
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getResourceColumns,
|
getResourceColumns,
|
||||||
getResourceFieldsFactory,
|
|
||||||
getResourceMetadata,
|
getResourceMetadata,
|
||||||
getResourceData,
|
getResourceFieldsFactory,
|
||||||
} from 'store/resources/resources.reducer';
|
getResourceDataFactory,
|
||||||
|
} from 'store/resources/resources.selectors';
|
||||||
|
|
||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
const getResourceFields = getResourceFieldsFactory();
|
const getResourceFields = getResourceFieldsFactory();
|
||||||
|
const getResourceData = getResourceDataFactory();
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const { resourceName } = props;
|
const { resourceName } = props;
|
||||||
|
|
||||||
const mapped = {
|
const mapped = {
|
||||||
resourceData: getResourceData(state, resourceName),
|
resourceData: getResourceData(state, props),
|
||||||
resourceFields: getResourceFields(state, props),
|
resourceFields: getResourceFields(state, props),
|
||||||
resourceColumns: getResourceColumns(state, resourceName),
|
resourceColumns: getResourceColumns(state, resourceName),
|
||||||
resourceMetadata: getResourceMetadata(state, resourceName),
|
resourceMetadata: getResourceMetadata(state, resourceName),
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
export const mapStateToProps = (state, props) => ({
|
export default (mapState) => {
|
||||||
organizationSettings: state.settings.data.organization,
|
const mapStateToProps = (state, props) => {
|
||||||
manualJournalsSettings: state.settings.data.manual_journals,
|
const mapped = {
|
||||||
});
|
organizationSettings: state.settings.data.organization,
|
||||||
|
manualJournalsSettings: state.settings.data.manual_journals,
|
||||||
|
};
|
||||||
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps);
|
return connect(mapStateToProps);
|
||||||
|
}
|
||||||
|
|||||||
@@ -765,5 +765,9 @@ export default {
|
|||||||
something_wentwrong: 'Something went wrong.',
|
something_wentwrong: 'Something went wrong.',
|
||||||
new_password: 'New password',
|
new_password: 'New password',
|
||||||
license_code_: 'License code',
|
license_code_: 'License code',
|
||||||
legal_organization_name: 'Legal Organization Name'
|
legal_organization_name: 'Legal Organization Name',
|
||||||
|
smaller_than: 'Smaller than',
|
||||||
|
smaller_or_equals: 'Smaller or equals',
|
||||||
|
bigger_than: 'Bigger than',
|
||||||
|
bigger_or_equals: 'Bigger or equals',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import t from 'store/types';
|
|||||||
export const submitEstimate = ({ form }) => {
|
export const submitEstimate = ({ form }) => {
|
||||||
return (dispatch) =>
|
return (dispatch) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
|
||||||
});
|
|
||||||
ApiService.post('sales/estimates', form)
|
ApiService.post('sales/estimates', form)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ export const deleteInvoice = ({ id }) => {
|
|||||||
export const editInvoice = (id, form) => {
|
export const editInvoice = (id, form) => {
|
||||||
return (dispatch) =>
|
return (dispatch) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
|
||||||
});
|
|
||||||
ApiService.post(`sales/invoices/${id}`, form)
|
ApiService.post(`sales/invoices/${id}`, form)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
resolve(response);
|
resolve(response);
|
||||||
|
|||||||
@@ -60,9 +60,6 @@ export const fetchAccountsTable = ({ query } = {}) => {
|
|||||||
type: t.ACCOUNTS_TABLE_LOADING,
|
type: t.ACCOUNTS_TABLE_LOADING,
|
||||||
loading: true,
|
loading: true,
|
||||||
});
|
});
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
|
||||||
});
|
|
||||||
ApiService.get('accounts', { params: { ...pageQuery, ...query } })
|
ApiService.get('accounts', { params: { ...pageQuery, ...query } })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -105,9 +102,6 @@ export const fetchAccountsDataTable = ({ query }) => {
|
|||||||
export const submitAccount = ({ form }) => {
|
export const submitAccount = ({ form }) => {
|
||||||
return (dispatch) =>
|
return (dispatch) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
|
||||||
});
|
|
||||||
ApiService.post('accounts', form)
|
ApiService.post('accounts', form)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -136,9 +130,6 @@ export const submitAccount = ({ form }) => {
|
|||||||
export const editAccount = (id, form) => {
|
export const editAccount = (id, form) => {
|
||||||
return (dispatch) =>
|
return (dispatch) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
|
||||||
});
|
|
||||||
ApiService.post(`accounts/${id}`, form)
|
ApiService.post(`accounts/${id}`, form)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch({ type: t.CLEAR_ACCOUNT_FORM_ERRORS });
|
dispatch({ type: t.CLEAR_ACCOUNT_FORM_ERRORS });
|
||||||
|
|||||||
@@ -10,17 +10,12 @@ export const submitCustomer = ({ form }) => {
|
|||||||
|
|
||||||
ApiService.post('customers', form)
|
ApiService.post('customers', 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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -35,17 +30,12 @@ export const editCustomer = ({ form, id }) => {
|
|||||||
|
|
||||||
ApiService.post(`customers/${id}`, form)
|
ApiService.post(`customers/${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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -59,9 +49,6 @@ export const fetchCustomers = ({ query }) => {
|
|||||||
type: t.ITEMS_TABLE_LOADING,
|
type: t.ITEMS_TABLE_LOADING,
|
||||||
payload: { loading: true },
|
payload: { loading: true },
|
||||||
});
|
});
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
|
||||||
});
|
|
||||||
ApiService.get(`customers`, { params: { ...pageQuery, ...query } })
|
ApiService.get(`customers`, { params: { ...pageQuery, ...query } })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -79,15 +66,9 @@ export const fetchCustomers = ({ query }) => {
|
|||||||
type: t.CUSTOMERS_TABLE_LOADING,
|
type: t.CUSTOMERS_TABLE_LOADING,
|
||||||
payload: { loading: false },
|
payload: { loading: false },
|
||||||
});
|
});
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
|
||||||
});
|
|
||||||
resolve(response);
|
resolve(response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
|
||||||
});
|
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createReducer } from '@reduxjs/toolkit';
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
pageTitle: '',
|
pageTitle: '',
|
||||||
pageSubtitle: 'Hello World',
|
pageSubtitle: '',
|
||||||
preferencesPageTitle: '',
|
preferencesPageTitle: '',
|
||||||
sidebarExpended: true,
|
sidebarExpended: true,
|
||||||
dialogs: {},
|
dialogs: {},
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { createSelector } from "@reduxjs/toolkit";
|
import { createSelector } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
const dialogByNameSelector = (dialogName) => (state) => state.dashboard.dialogs?.[dialogName];
|
const dialogByNameSelector = (state, props) => state.dashboard.dialogs?.[props.dialogName];
|
||||||
|
|
||||||
export const isDialogOpenFactory = (dialogName) => createSelector(
|
export const isDialogOpenFactory = () => createSelector(
|
||||||
dialogByNameSelector(dialogName),
|
dialogByNameSelector,
|
||||||
(dialog) => {
|
(dialog) => {
|
||||||
return dialog && dialog.isOpen;
|
return dialog && dialog.isOpen;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getDialogPayloadFactory = (dialogName) => createSelector(
|
export const getDialogPayloadFactory = () => createSelector(
|
||||||
dialogByNameSelector(dialogName),
|
dialogByNameSelector,
|
||||||
(dialog) => {
|
(dialog) => {
|
||||||
return { ...dialog?.payload };
|
return { ...dialog?.payload };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,9 +18,6 @@ export const fetchItems = ({ query }) => {
|
|||||||
type: t.ITEMS_TABLE_LOADING,
|
type: t.ITEMS_TABLE_LOADING,
|
||||||
payload: { loading: true },
|
payload: { loading: true },
|
||||||
});
|
});
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
|
||||||
});
|
|
||||||
ApiService.get(`items`, { params: { ...pageQuery, ...query } })
|
ApiService.get(`items`, { params: { ...pageQuery, ...query } })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import t from 'store/types';
|
|||||||
export const submitReceipt = ({ form }) => {
|
export const submitReceipt = ({ form }) => {
|
||||||
return (dispatch) =>
|
return (dispatch) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
|
||||||
});
|
|
||||||
ApiService.post('sales/receipts', form)
|
ApiService.post('sales/receipts', form)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|||||||
@@ -20,14 +20,11 @@ export const fetchResourceColumns = ({ resourceSlug }) => {
|
|||||||
export const fetchResourceFields = ({ resourceSlug }) => {
|
export const fetchResourceFields = ({ resourceSlug }) => {
|
||||||
return (dispatch) => new Promise((resolve, reject) => {
|
return (dispatch) => new Promise((resolve, reject) => {
|
||||||
ApiService.get(`resources/${resourceSlug}/fields`).then((response) => {
|
ApiService.get(`resources/${resourceSlug}/fields`).then((response) => {
|
||||||
// dispatch({
|
dispatch({
|
||||||
// type: t.RESOURCE_FIELDS_SET,
|
type: t.RESOURCE_FIELDS_SET,
|
||||||
// fields: response.data.resource_fields,
|
fields: response.data.resource_fields,
|
||||||
// resource_slug: resourceSlug,
|
resource_slug: resourceSlug,
|
||||||
// });
|
});
|
||||||
// dispatch({
|
|
||||||
// type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
|
||||||
// });
|
|
||||||
resolve(response);
|
resolve(response);
|
||||||
}).catch((error) => { reject(error); });
|
}).catch((error) => { reject(error); });
|
||||||
});
|
});
|
||||||
@@ -36,13 +33,13 @@ export const fetchResourceFields = ({ resourceSlug }) => {
|
|||||||
export const fetchResourceData = ({ resourceSlug }) => {
|
export const fetchResourceData = ({ resourceSlug }) => {
|
||||||
return (dispatch) => new Promise((resolve, reject) => {
|
return (dispatch) => new Promise((resolve, reject) => {
|
||||||
ApiService.get(`/resources/${resourceSlug}/data`).then((response) => {
|
ApiService.get(`/resources/${resourceSlug}/data`).then((response) => {
|
||||||
// dispatch({
|
dispatch({
|
||||||
// type: t.RESOURCE_DATA_SET,
|
type: t.RESOURCE_DATA_SET,
|
||||||
// payload: {
|
payload: {
|
||||||
// data: response.data.data,
|
data: response.data.resource_data,
|
||||||
// resource_key: resourceSlug,
|
resourceKey: resourceSlug,
|
||||||
// },
|
},
|
||||||
// });
|
});
|
||||||
resolve(response);
|
resolve(response);
|
||||||
}).catch(error => { reject(error); });
|
}).catch(error => { reject(error); });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { createReducer } from "@reduxjs/toolkit";
|
import { createReducer } from "@reduxjs/toolkit";
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
import { pickItemsFromIds } from 'store/selectors'
|
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
data: {},
|
data: {
|
||||||
|
resources: {},
|
||||||
|
},
|
||||||
fields: {},
|
fields: {},
|
||||||
columns: {},
|
columns: {},
|
||||||
resourceFields: {},
|
resourceFields: {},
|
||||||
@@ -44,77 +44,28 @@ export default createReducer(initialState, {
|
|||||||
const _fields = {};
|
const _fields = {};
|
||||||
|
|
||||||
action.fields.forEach((field) => {
|
action.fields.forEach((field) => {
|
||||||
_fields[field.id] = field;
|
_fields[field.key] = field;
|
||||||
});
|
});
|
||||||
state.fields = {
|
state.fields = {
|
||||||
...state.fields,
|
...state.fields,
|
||||||
..._fields,
|
..._fields,
|
||||||
};
|
};
|
||||||
state.resourceFields[action.resource_slug] = action.fields.map(f => f.id);
|
state.resourceFields[action.resource_slug] = action.fields.map(f => f.key);
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.RESOURCE_DATA_SET]: (state, action) => {
|
[t.RESOURCE_DATA_SET]: (state, action) => {
|
||||||
const { data, resource_key: resourceKey } = action.payload;
|
const { data, resourceKey } = action.payload;
|
||||||
const dataMapped = {};
|
const _data = {};
|
||||||
|
|
||||||
data.forEach((item) => { dataMapped[item.id] = item; })
|
data.forEach((item) => {
|
||||||
state.data[resourceKey] = dataMapped;
|
_data[item.id] = item;
|
||||||
|
});
|
||||||
|
const order = data.map((item) => item.id);
|
||||||
|
|
||||||
|
state.data.resources[resourceKey] = {
|
||||||
|
...(state.data.resources[resourceKey] || {}),
|
||||||
|
data: _data,
|
||||||
|
order,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const resourceFieldsIdsSelector = (state, props) => state.resources.resourceFields[props.resourceName];
|
|
||||||
const resourceFieldsItemsSelector = (state) => state.resources.fields;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve resource fields of the given resource slug.
|
|
||||||
* @param {Object} state
|
|
||||||
* @param {String} resourceSlug
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
export const getResourceFieldsFactory = () => createSelector(
|
|
||||||
resourceFieldsIdsSelector,
|
|
||||||
resourceFieldsItemsSelector,
|
|
||||||
(fieldsIds, fieldsItems) => {
|
|
||||||
return pickItemsFromIds(fieldsItems, fieldsIds);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve resource columns of the given resource slug.
|
|
||||||
* @param {State} state
|
|
||||||
* @param {String} resourceSlug -
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
export const getResourceColumns = (state, resourceSlug) => {
|
|
||||||
const resourceIds = state.resources.resourceColumns[resourceSlug];
|
|
||||||
const items = state.resources.columns;
|
|
||||||
return pickItemsFromIds(items, resourceIds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {State} state
|
|
||||||
* @param {Number} fieldId
|
|
||||||
*/
|
|
||||||
export const getResourceField = (state, fieldId) => {
|
|
||||||
return state.resources.fields[fieldId];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {State} state
|
|
||||||
* @param {Number} columnId
|
|
||||||
*/
|
|
||||||
export const getResourceColumn = (state, columnId) => {
|
|
||||||
return state.resources.columns[columnId];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getResourceMetadata = (state, resourceSlug) => {
|
|
||||||
return state.resources.metadata[resourceSlug];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const getResourceData = (state, resourceSlug) => {
|
|
||||||
return state.resources.data[resourceSlug] || {};
|
|
||||||
};
|
|
||||||
77
client/src/store/resources/resources.selectors.js
Normal file
77
client/src/store/resources/resources.selectors.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { pickItemsFromIds } from 'store/selectors';
|
||||||
|
|
||||||
|
const resourceDataIdsSelector = (state, props) => {
|
||||||
|
return state.resources.data.resources[props.resourceName]?.order;
|
||||||
|
}
|
||||||
|
const resourceDataSelector = (state, props) => {
|
||||||
|
return state.resources.data.resources[props.resourceName]?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourceFieldsIdsSelector = (state, props) => state.resources.resourceFields[props.resourceName];
|
||||||
|
const resourceFieldsItemsSelector = (state) => state.resources.fields;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve resource fields of the given resource slug.
|
||||||
|
* @param {Object} state
|
||||||
|
* @param {String} resourceSlug
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
export const getResourceFieldsFactory = () => createSelector(
|
||||||
|
resourceFieldsIdsSelector,
|
||||||
|
resourceFieldsItemsSelector,
|
||||||
|
(fieldsIds, fieldsItems) => {
|
||||||
|
return pickItemsFromIds(fieldsItems, fieldsIds);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve resource data of the given resource name in component properties.
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
export const getResourceDataFactory = () => createSelector(
|
||||||
|
resourceDataSelector,
|
||||||
|
resourceDataIdsSelector,
|
||||||
|
(resourceData, resourceDataIds) => {
|
||||||
|
return pickItemsFromIds(resourceData, resourceDataIds);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve resource columns of the given resource slug.
|
||||||
|
* @param {State} state
|
||||||
|
* @param {String} resourceSlug -
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
export const getResourceColumns = (state, resourceSlug) => {
|
||||||
|
const resourceIds = state.resources.resourceColumns[resourceSlug];
|
||||||
|
const items = state.resources.columns;
|
||||||
|
return pickItemsFromIds(items, resourceIds);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {State} state
|
||||||
|
* @param {Number} fieldId
|
||||||
|
*/
|
||||||
|
export const getResourceField = (state, fieldId) => {
|
||||||
|
return state.resources.fields[fieldId];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {State} state
|
||||||
|
* @param {Number} columnId
|
||||||
|
*/
|
||||||
|
export const getResourceColumn = (state, columnId) => {
|
||||||
|
return state.resources.columns[columnId];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getResourceMetadata = (state, resourceSlug) => {
|
||||||
|
return state.resources.metadata[resourceSlug];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getResourceData = (state, resourceSlug) => {
|
||||||
|
return state.resources.data[resourceSlug] || {};
|
||||||
|
};
|
||||||
4
client/src/store/vendors/vendors.actions.js
vendored
4
client/src/store/vendors/vendors.actions.js
vendored
@@ -79,10 +79,6 @@ export const deleteVendor = ({ id }) => {
|
|||||||
export const submitVendor = ({ form }) => {
|
export const submitVendor = ({ form }) => {
|
||||||
return (dispatch) =>
|
return (dispatch) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
dispatch({
|
|
||||||
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
|
||||||
});
|
|
||||||
|
|
||||||
ApiService.post('vendors', form)
|
ApiService.post('vendors', form)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|||||||
@@ -6,4 +6,9 @@
|
|||||||
&-header{
|
&-header{
|
||||||
background: #ebf1f5;
|
background: #ebf1f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bp3-spinner{
|
||||||
|
padding-top: 10px;
|
||||||
|
margin-bottom: -10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
"nodemailer": "^6.3.0",
|
"nodemailer": "^6.3.0",
|
||||||
"nodemon": "^1.19.1",
|
"nodemon": "^1.19.1",
|
||||||
"objection": "^2.0.10",
|
"objection": "^2.0.10",
|
||||||
|
"objection-filter": "^4.0.1",
|
||||||
"objection-soft-delete": "^1.0.7",
|
"objection-soft-delete": "^1.0.7",
|
||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
|
|||||||
@@ -24,9 +24,18 @@ export default class ResourceController extends BaseController{
|
|||||||
'/:resource_model/fields', [
|
'/:resource_model/fields', [
|
||||||
...this.resourceModelParamSchema,
|
...this.resourceModelParamSchema,
|
||||||
],
|
],
|
||||||
|
this.validationResult,
|
||||||
asyncMiddleware(this.resourceFields.bind(this)),
|
asyncMiddleware(this.resourceFields.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/:resource_model/data', [
|
||||||
|
...this.resourceModelParamSchema,
|
||||||
|
],
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.resourceData.bind(this)),
|
||||||
|
this.handleServiceErrors,
|
||||||
|
)
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +66,28 @@ export default class ResourceController extends BaseController{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve resource data of the give resource based on the given query.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
async resourceData(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { resource_model: resourceModel } = req.params;
|
||||||
|
const filter = req.query;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resourceData = await this.resourcesService.getResourceData(tenantId, resourceModel, filter);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
resource_data: this.transfromToResponse(resourceData),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles service errors.
|
* Handles service errors.
|
||||||
* @param {Error} error
|
* @param {Error} error
|
||||||
@@ -72,5 +103,6 @@ export default class ResourceController extends BaseController{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -113,121 +113,6 @@ export default class SalesReceiptsController extends BaseController{
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate whether sale receipt exists on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
*/
|
|
||||||
async validateSaleReceiptExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
const { id: saleReceiptId } = req.params;
|
|
||||||
|
|
||||||
const isSaleReceiptExists = await this.saleReceiptService
|
|
||||||
.isSaleReceiptExists(
|
|
||||||
tenantId,
|
|
||||||
saleReceiptId,
|
|
||||||
);
|
|
||||||
if (!isSaleReceiptExists) {
|
|
||||||
return res.status(404).send({
|
|
||||||
errors: [{ type: 'SALE.RECEIPT.NOT.FOUND', code: 200 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate whether sale receipt customer exists on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateReceiptCustomerExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const saleReceipt = { ...req.body };
|
|
||||||
const { Customer } = req.models;
|
|
||||||
|
|
||||||
const foundCustomer = await Customer.query().findById(saleReceipt.customer_id);
|
|
||||||
|
|
||||||
if (!foundCustomer) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'CUSTOMER.ID.NOT.EXISTS', code: 200 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate whether sale receipt deposit account exists on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateReceiptDepositAccountExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
|
|
||||||
const saleReceipt = { ...req.body };
|
|
||||||
const isDepositAccountExists = await this.accountsService.isAccountExists(
|
|
||||||
tenantId,
|
|
||||||
saleReceipt.deposit_account_id
|
|
||||||
);
|
|
||||||
if (!isDepositAccountExists) {
|
|
||||||
return res.status(400).send({
|
|
||||||
errors: [{ type: 'DEPOSIT.ACCOUNT.NOT.EXISTS', code: 300 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate whether receipt items ids exist on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateReceiptItemsIdsExistance(req: Request, res: Response, next: Function) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
|
|
||||||
const saleReceipt = { ...req.body };
|
|
||||||
const estimateItemsIds = saleReceipt.entries.map((e) => e.item_id);
|
|
||||||
|
|
||||||
const notFoundItemsIds = await this.itemsService.isItemsIdsExists(
|
|
||||||
tenantId,
|
|
||||||
estimateItemsIds
|
|
||||||
);
|
|
||||||
if (notFoundItemsIds.length > 0) {
|
|
||||||
return res.status(400).send({ errors: [{ type: 'ITEMS.IDS.NOT.EXISTS', code: 400 }] });
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate receipt entries ids existance on the storage.
|
|
||||||
* @param {Request} req
|
|
||||||
* @param {Response} res
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
async validateReceiptEntriesIds(req: Request, res: Response, next: Function) {
|
|
||||||
const { tenantId } = req;
|
|
||||||
|
|
||||||
const saleReceipt = { ...req.body };
|
|
||||||
const { id: saleReceiptId } = req.params;
|
|
||||||
|
|
||||||
// Validate the entries IDs that not stored or associated to the sale receipt.
|
|
||||||
const notExistsEntriesIds = await this.saleReceiptService
|
|
||||||
.isSaleReceiptEntriesIDsExists(
|
|
||||||
tenantId,
|
|
||||||
saleReceiptId,
|
|
||||||
saleReceipt,
|
|
||||||
);
|
|
||||||
if (notExistsEntriesIds.length > 0) {
|
|
||||||
return res.status(400).send({ errors: [{
|
|
||||||
type: 'ENTRIES.IDS.NOT.FOUND',
|
|
||||||
code: 500,
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new receipt.
|
* Creates a new receipt.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
@@ -244,7 +129,10 @@ export default class SalesReceiptsController extends BaseController{
|
|||||||
tenantId,
|
tenantId,
|
||||||
saleReceiptDTO,
|
saleReceiptDTO,
|
||||||
);
|
);
|
||||||
return res.status(200).send({ id: storedSaleReceipt.id });
|
return res.status(200).send({
|
||||||
|
id: storedSaleReceipt.id,
|
||||||
|
message: 'Sale receipt has been created successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -263,7 +151,10 @@ export default class SalesReceiptsController extends BaseController{
|
|||||||
// Deletes the sale receipt.
|
// Deletes the sale receipt.
|
||||||
await this.saleReceiptService.deleteSaleReceipt(tenantId, saleReceiptId);
|
await this.saleReceiptService.deleteSaleReceipt(tenantId, saleReceiptId);
|
||||||
|
|
||||||
return res.status(200).send({ id: saleReceiptId });
|
return res.status(200).send({
|
||||||
|
id: saleReceiptId,
|
||||||
|
message: 'Sale receipt has been deleted successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -287,7 +178,9 @@ export default class SalesReceiptsController extends BaseController{
|
|||||||
saleReceiptId,
|
saleReceiptId,
|
||||||
saleReceipt,
|
saleReceipt,
|
||||||
);
|
);
|
||||||
return res.status(200).send();
|
return res.status(200).send({
|
||||||
|
message: 'Sale receipt has been edited successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
|||||||
this.setResponseMeta();
|
this.setResponseMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds filter roles logic expression.
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
private buildLogicExpression(): string {
|
private buildLogicExpression(): string {
|
||||||
let expression = '';
|
let expression = '';
|
||||||
this.filterRoles.forEach((role, index) => {
|
this.filterRoles.forEach((role, index) => {
|
||||||
|
|||||||
@@ -42,12 +42,14 @@ const numberRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) =>
|
|||||||
const textRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
const textRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
||||||
switch (role.comparator) {
|
switch (role.comparator) {
|
||||||
case 'equals':
|
case 'equals':
|
||||||
|
case 'is':
|
||||||
default:
|
default:
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
builder.where(comparatorColumn, role.value);
|
builder.where(comparatorColumn, role.value);
|
||||||
};
|
};
|
||||||
case 'not_equal':
|
case 'not_equal':
|
||||||
case 'not_equals':
|
case 'not_equals':
|
||||||
|
case 'is_not':
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
builder.whereNot(comparatorColumn, role.value);
|
builder.whereNot(comparatorColumn, role.value);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"Journal": "Journal",
|
"Journal": "Journal",
|
||||||
"Reconciliation": "Reconciliation",
|
"Reconciliation": "Reconciliation",
|
||||||
"Credit": "Credit",
|
"Credit": "Credit",
|
||||||
|
"Debit": "Debit",
|
||||||
"Interest": "Interest",
|
"Interest": "Interest",
|
||||||
"Depreciation": "Depreciation",
|
"Depreciation": "Depreciation",
|
||||||
"Payroll": "Payroll",
|
"Payroll": "Payroll",
|
||||||
@@ -67,5 +68,10 @@
|
|||||||
"Journal number": "Journal number",
|
"Journal number": "Journal number",
|
||||||
"Status": "Status",
|
"Status": "Status",
|
||||||
"Journal type": "Journal type",
|
"Journal type": "Journal type",
|
||||||
"Date": "Date"
|
"Date": "Date",
|
||||||
|
"Asset": "Asset",
|
||||||
|
"Liability": "Liability",
|
||||||
|
"First-in first-out (FIFO)": "First-in first-out (FIFO)",
|
||||||
|
"Last-in first-out (LIFO)": "Last-in first-out (LIFO)",
|
||||||
|
"Average rate": "Average rate"
|
||||||
}
|
}
|
||||||
@@ -123,50 +123,84 @@ export default class Account extends TenantModel {
|
|||||||
name: {
|
name: {
|
||||||
label: 'Account name',
|
label: 'Account name',
|
||||||
column: 'name',
|
column: 'name',
|
||||||
|
columnType: 'string',
|
||||||
|
|
||||||
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
label: 'Account type',
|
label: 'Account type',
|
||||||
column: 'account_type_id',
|
column: 'account_type_id',
|
||||||
relation: 'account_types.id',
|
relation: 'account_types.id',
|
||||||
relationColumn: 'account_types.key',
|
relationColumn: 'account_types.key',
|
||||||
|
|
||||||
|
fieldType: 'options',
|
||||||
|
optionsResource: 'AccountType',
|
||||||
|
optionsKey: 'key',
|
||||||
|
optionsLabel: 'label',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
label: 'Description',
|
label: 'Description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
|
columnType: 'string',
|
||||||
|
|
||||||
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
label: 'Account code',
|
label: 'Account code',
|
||||||
column: 'code',
|
column: 'code',
|
||||||
|
columnType: 'string',
|
||||||
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
root_type: {
|
root_type: {
|
||||||
label: 'Type',
|
label: 'Root type',
|
||||||
column: 'account_type_id',
|
column: 'account_type_id',
|
||||||
relation: 'account_types.id',
|
relation: 'account_types.id',
|
||||||
relationColumn: 'account_types.root_type',
|
relationColumn: 'account_types.root_type',
|
||||||
|
options: [
|
||||||
|
{ key: 'asset', label: 'Asset', },
|
||||||
|
{ key: 'liability', label: 'Liability' },
|
||||||
|
{ key: 'equity', label: 'Equity' },
|
||||||
|
{ key: 'Income', label: 'Income' },
|
||||||
|
{ key: 'expense', label: 'Expense' },
|
||||||
|
],
|
||||||
|
fieldType: 'options',
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
columnType: 'date',
|
columnType: 'date',
|
||||||
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
label: 'Active',
|
label: 'Active',
|
||||||
column: 'active',
|
column: 'active',
|
||||||
|
columnType: 'boolean',
|
||||||
|
fieldType: 'checkbox',
|
||||||
},
|
},
|
||||||
balance: {
|
balance: {
|
||||||
label: 'Balance',
|
label: 'Balance',
|
||||||
column: 'amount',
|
column: 'amount',
|
||||||
columnType: 'number'
|
columnType: 'number',
|
||||||
|
fieldType: 'number',
|
||||||
},
|
},
|
||||||
currency: {
|
currency: {
|
||||||
label: 'Currency',
|
label: 'Currency',
|
||||||
column: 'currency_code',
|
column: 'currency_code',
|
||||||
|
fieldType: 'options',
|
||||||
|
optionsResource: 'currency',
|
||||||
|
optionsKey: 'currency_code',
|
||||||
|
optionsLabel: 'currency_name',
|
||||||
},
|
},
|
||||||
normal: {
|
normal: {
|
||||||
label: 'Account normal',
|
label: 'Account normal',
|
||||||
column: 'account_type_id',
|
column: 'account_type_id',
|
||||||
|
fieldType: 'options',
|
||||||
relation: 'account_types.id',
|
relation: 'account_types.id',
|
||||||
relationColumn: 'account_types.normal'
|
relationColumn: 'account_types.normal',
|
||||||
|
options: [
|
||||||
|
{ key: 'credit', label: 'Credit' },
|
||||||
|
{ key: 'debit', label: 'Debit' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ export default class AccountType extends TenantModel {
|
|||||||
return ['label'];
|
return ['label'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to mark model as resourceable to viewable and filterable.
|
||||||
|
*/
|
||||||
|
static get resourceable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translatable lable.
|
* Translatable lable.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,4 +14,8 @@ export default class Currency extends TenantModel {
|
|||||||
get timestamps() {
|
get timestamps() {
|
||||||
return ['createdAt', 'updatedAt'];
|
return ['createdAt', 'updatedAt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get resourceable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,31 +127,38 @@ export default class Expense extends TenantModel {
|
|||||||
payment_date: {
|
payment_date: {
|
||||||
label: 'Payment date',
|
label: 'Payment date',
|
||||||
column: 'payment_date',
|
column: 'payment_date',
|
||||||
|
columnType: 'date',
|
||||||
},
|
},
|
||||||
payment_account: {
|
payment_account: {
|
||||||
label: 'Payment account',
|
label: 'Payment account',
|
||||||
column: 'payment_account_id',
|
column: 'payment_account_id',
|
||||||
relation: 'accounts.id',
|
relation: 'accounts.id',
|
||||||
|
optionsResource: 'account',
|
||||||
},
|
},
|
||||||
amount: {
|
amount: {
|
||||||
label: 'Amount',
|
label: 'Amount',
|
||||||
column: 'total_amount',
|
column: 'total_amount',
|
||||||
|
columnType: 'number'
|
||||||
},
|
},
|
||||||
currency_code: {
|
currency_code: {
|
||||||
label: 'Currency',
|
label: 'Currency',
|
||||||
column: 'currency_code',
|
column: 'currency_code',
|
||||||
|
optionsResource: 'currency',
|
||||||
},
|
},
|
||||||
reference_no: {
|
reference_no: {
|
||||||
label: 'Reference No.',
|
label: 'Reference No.',
|
||||||
column: 'reference_no'
|
column: 'reference_no',
|
||||||
|
columnType: 'string',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
label: 'Description',
|
label: 'Description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
|
columnType: 'string',
|
||||||
},
|
},
|
||||||
published: {
|
published: {
|
||||||
label: 'Published',
|
label: 'Published',
|
||||||
column: 'published',
|
column: 'published',
|
||||||
|
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
label: 'User',
|
label: 'User',
|
||||||
@@ -162,6 +169,7 @@ export default class Expense extends TenantModel {
|
|||||||
created_at: {
|
created_at: {
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
|
columnType: 'date',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,16 +50,19 @@ export default class ItemCategory extends TenantModel {
|
|||||||
name: {
|
name: {
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
column: 'name',
|
column: 'name',
|
||||||
|
columnType: 'string'
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
label: 'Description',
|
label: 'Description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
|
columnType: 'string'
|
||||||
},
|
},
|
||||||
parent_category_id: {
|
parent_category_id: {
|
||||||
label: 'Parent category',
|
label: 'Parent category',
|
||||||
column: 'parent_category_id',
|
column: 'parent_category_id',
|
||||||
relation: 'items_categories.id',
|
relation: 'items_categories.id',
|
||||||
relationColumn: 'items_categories.id',
|
relationColumn: 'items_categories.id',
|
||||||
|
optionsResource: 'item_category',
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
label: 'User',
|
label: 'User',
|
||||||
@@ -71,24 +74,34 @@ export default class ItemCategory extends TenantModel {
|
|||||||
label: 'Cost account',
|
label: 'Cost account',
|
||||||
column: 'cost_account_id',
|
column: 'cost_account_id',
|
||||||
relation: 'accounts.id',
|
relation: 'accounts.id',
|
||||||
|
optionsResource: 'account'
|
||||||
},
|
},
|
||||||
sell_account: {
|
sell_account: {
|
||||||
label: 'Sell account',
|
label: 'Sell account',
|
||||||
column: 'sell_account_id',
|
column: 'sell_account_id',
|
||||||
relation: 'accounts.id',
|
relation: 'accounts.id',
|
||||||
|
optionsResource: 'account'
|
||||||
},
|
},
|
||||||
inventory_account: {
|
inventory_account: {
|
||||||
label: 'Inventory account',
|
label: 'Inventory account',
|
||||||
column: 'inventory_account_id',
|
column: 'inventory_account_id',
|
||||||
relation: 'accounts.id',
|
relation: 'accounts.id',
|
||||||
|
optionsResource: 'account'
|
||||||
},
|
},
|
||||||
cost_method: {
|
cost_method: {
|
||||||
label: 'Cost method',
|
label: 'Cost method',
|
||||||
column: 'cost_method',
|
column: 'cost_method',
|
||||||
|
options: [{
|
||||||
|
key: 'FIFO', label: 'First-in first-out (FIFO)',
|
||||||
|
key: 'LIFO', label: 'Last-in first-out (LIFO)',
|
||||||
|
key: 'average', label: 'Average rate',
|
||||||
|
}],
|
||||||
|
columnType: 'string',
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
|
columnType: 'date',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,14 +66,17 @@ export default class ManualJournal extends TenantModel {
|
|||||||
date: {
|
date: {
|
||||||
label: 'Date',
|
label: 'Date',
|
||||||
column: 'date',
|
column: 'date',
|
||||||
|
columnType: 'date',
|
||||||
},
|
},
|
||||||
journal_number: {
|
journal_number: {
|
||||||
label: 'Journal number',
|
label: 'Journal number',
|
||||||
column: 'journal_number',
|
column: 'journal_number',
|
||||||
|
columnType: 'string',
|
||||||
},
|
},
|
||||||
reference: {
|
reference: {
|
||||||
label: 'Reference No.',
|
label: 'Reference No.',
|
||||||
column: 'reference',
|
column: 'reference',
|
||||||
|
columnType: 'string',
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
@@ -82,10 +85,12 @@ export default class ManualJournal extends TenantModel {
|
|||||||
amount: {
|
amount: {
|
||||||
label: 'Amount',
|
label: 'Amount',
|
||||||
column: 'amount',
|
column: 'amount',
|
||||||
|
columnType: 'number',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
label: 'Description',
|
label: 'Description',
|
||||||
column: 'description',
|
column: 'description',
|
||||||
|
columnType: 'string',
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
label: 'User',
|
label: 'User',
|
||||||
|
|||||||
@@ -70,8 +70,6 @@ export default class DynamicListService implements IDynamicListService {
|
|||||||
private validateRolesFieldsExistance(model: IModel, filterRoles: IFilterRole[]) {
|
private validateRolesFieldsExistance(model: IModel, filterRoles: IFilterRole[]) {
|
||||||
const invalidFieldsKeys = validateFilterRolesFieldsExistance(model, filterRoles);
|
const invalidFieldsKeys = validateFilterRolesFieldsExistance(model, filterRoles);
|
||||||
|
|
||||||
console.log(invalidFieldsKeys);
|
|
||||||
|
|
||||||
if (invalidFieldsKeys.length > 0) {
|
if (invalidFieldsKeys.length > 0) {
|
||||||
throw new ServiceError(ERRORS.FILTER_ROLES_FIELDS_NOT_FOUND);
|
throw new ServiceError(ERRORS.FILTER_ROLES_FIELDS_NOT_FOUND);
|
||||||
}
|
}
|
||||||
@@ -86,8 +84,9 @@ export default class DynamicListService implements IDynamicListService {
|
|||||||
required: true,
|
required: true,
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
condition: { type: 'string' },
|
||||||
fieldKey: { required: true, type: 'string' },
|
fieldKey: { required: true, type: 'string' },
|
||||||
value: { required: true, type: 'string' },
|
value: { required: true },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const invalidFields = filterRoles.filter((filterRole) => {
|
const invalidFields = filterRoles.filter((filterRole) => {
|
||||||
@@ -126,12 +125,16 @@ export default class DynamicListService implements IDynamicListService {
|
|||||||
}
|
}
|
||||||
// Filter roles.
|
// Filter roles.
|
||||||
if (filter.filterRoles.length > 0) {
|
if (filter.filterRoles.length > 0) {
|
||||||
this.validateFilterRolesSchema(filter.filterRoles);
|
const filterRoles = filter.filterRoles.map((filterRole, index) => ({
|
||||||
this.validateRolesFieldsExistance(model, filter.filterRoles);
|
...filterRole,
|
||||||
|
index: index + 1,
|
||||||
|
}));
|
||||||
|
this.validateFilterRolesSchema(filterRoles);
|
||||||
|
this.validateRolesFieldsExistance(model, filterRoles);
|
||||||
|
|
||||||
// Validate the model resource fields.
|
// Validate the model resource fields.
|
||||||
const filterRoles = new DynamicFilterFilterRoles(filter.filterRoles);
|
const dynamicFilterRoles = new DynamicFilterFilterRoles(filterRoles);
|
||||||
dynamicFilter.setFilter(filterRoles);
|
dynamicFilter.setFilter(dynamicFilterRoles);
|
||||||
}
|
}
|
||||||
return dynamicFilter;
|
return dynamicFilter;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { camelCase, upperFirst } from 'lodash';
|
import { camelCase, upperFirst } from 'lodash';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
|
import { buildFilter } from 'objection-filter';
|
||||||
import { IModel } from 'interfaces';
|
import { IModel } from 'interfaces';
|
||||||
import {
|
import {
|
||||||
getModelFields,
|
getModelFields,
|
||||||
@@ -35,18 +36,42 @@ export default class ResourceService {
|
|||||||
const fields = getModelFields(Model);
|
const fields = getModelFields(Model);
|
||||||
|
|
||||||
return fields.map((field) => ({
|
return fields.map((field) => ({
|
||||||
label: __(field.label, field.label),
|
label: __(field.label),
|
||||||
key: field.key,
|
key: field.key,
|
||||||
dataType: field.columnType,
|
dataType: field.columnType,
|
||||||
|
fieldType: field.fieldType,
|
||||||
|
|
||||||
|
...(field.options) ? {
|
||||||
|
options: field.options.map((option) => ({
|
||||||
|
...option, label: __(option.label),
|
||||||
|
})),
|
||||||
|
} : {},
|
||||||
|
|
||||||
|
...(field.optionsResource) ? {
|
||||||
|
optionsResource: field.optionsResource,
|
||||||
|
optionsKey: field.optionsKey,
|
||||||
|
optionsLabel: field.optionsLabel,
|
||||||
|
} : {},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should model be resource-able or throw service error.
|
||||||
|
* @param {IModel} model
|
||||||
|
*/
|
||||||
|
private shouldModelBeResourceable(model: IModel) {
|
||||||
|
if (!model.resourceable) {
|
||||||
|
throw new ServiceError(ERRORS.RESOURCE_MODEL_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve resource fields from resource model name.
|
* Retrieve resource fields from resource model name.
|
||||||
* @param {string} resourceName
|
* @param {string} resourceName
|
||||||
*/
|
*/
|
||||||
public getResourceFields(tenantId: number, modelName: string) {
|
public getResourceFields(tenantId: number, modelName: string) {
|
||||||
const resourceModel = this.getResourceModel(tenantId, modelName);
|
const resourceModel = this.getResourceModel(tenantId, modelName);
|
||||||
|
this.shouldModelBeResourceable(resourceModel);
|
||||||
|
|
||||||
return this.getModelFields(tenantId, resourceModel);
|
return this.getModelFields(tenantId, resourceModel);
|
||||||
}
|
}
|
||||||
@@ -63,9 +88,18 @@ export default class ResourceService {
|
|||||||
if (!Models[modelName]) {
|
if (!Models[modelName]) {
|
||||||
throw new ServiceError(ERRORS.RESOURCE_MODEL_NOT_FOUND);
|
throw new ServiceError(ERRORS.RESOURCE_MODEL_NOT_FOUND);
|
||||||
}
|
}
|
||||||
if (!Models[modelName].resourceable) {
|
|
||||||
throw new ServiceError(ERRORS.RESOURCE_MODEL_NOT_FOUND);
|
|
||||||
}
|
|
||||||
return Models[modelName];
|
return Models[modelName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve resource data from the storage based on the given query.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {string} modelName
|
||||||
|
*/
|
||||||
|
public async getResourceData(tenantId: number, modelName: string, filter: any) {
|
||||||
|
const resourceModel = this.getResourceModel(tenantId, modelName);
|
||||||
|
this.shouldModelBeResourceable(resourceModel);
|
||||||
|
|
||||||
|
return buildFilter(resourceModel).build(filter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user