diff --git a/client/src/components/Dialog.js b/client/src/components/Dialog.js deleted file mode 100644 index d06ef5b62..000000000 --- a/client/src/components/Dialog.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import {Dialog, Spinner, Classes} from '@blueprintjs/core'; - -export default function DialogComponent(props) { - const loadingContent = ( -
- ); - return ( - - {props.isLoading ? loadingContent : props.children} - - ); -} \ No newline at end of file diff --git a/client/src/components/Dialog/Dialog.js b/client/src/components/Dialog/Dialog.js new file mode 100644 index 000000000..be10d13ac --- /dev/null +++ b/client/src/components/Dialog/Dialog.js @@ -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 ( + + { children } + + ); +} + +export default compose( + withDialogActions, +)(DialogComponent); \ No newline at end of file diff --git a/client/src/components/Dialog/DialogContent.js b/client/src/components/Dialog/DialogContent.js new file mode 100644 index 000000000..f20418beb --- /dev/null +++ b/client/src/components/Dialog/DialogContent.js @@ -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 = ( +
+ ); + return ( +
+ {isLoading ? loadingContent : children} +
+ ); +} \ No newline at end of file diff --git a/client/src/components/Dialog/DialogSuspense.js b/client/src/components/Dialog/DialogSuspense.js new file mode 100644 index 000000000..6513d28c5 --- /dev/null +++ b/client/src/components/Dialog/DialogSuspense.js @@ -0,0 +1,18 @@ +import React, { Suspense } from 'react'; +import { Classes, Spinner } from '@blueprintjs/core'; + +function LoadingContent() { + return (
); +} + +export default function DialogSuspense({ + children +}) { + return ( + }> +
+ { children } +
+
+ ); +}; \ No newline at end of file diff --git a/client/src/components/DialogReduxConnect.js b/client/src/components/DialogReduxConnect.js index f126e5399..473302d77 100644 --- a/client/src/components/DialogReduxConnect.js +++ b/client/src/components/DialogReduxConnect.js @@ -1,17 +1,15 @@ -import React from 'react'; import { connect } from 'react-redux'; import { isDialogOpenFactory, getDialogPayloadFactory, } from 'store/dashboard/dashboard.selectors'; -export default (mapState, dialogName) => { - const isDialogOpen = isDialogOpenFactory(dialogName); - const getDialogPayload = getDialogPayloadFactory(dialogName); +export default (mapState) => { + const isDialogOpen = isDialogOpenFactory(); + const getDialogPayload = getDialogPayloadFactory(); const mapStateToProps = (state, props) => { const mapped = { - dialogName, isOpen: isDialogOpen(state, props), payload: getDialogPayload(state, props), }; diff --git a/client/src/components/DialogsContainer.js b/client/src/components/DialogsContainer.js index b8ff6d95b..f10c2fed8 100644 --- a/client/src/components/DialogsContainer.js +++ b/client/src/components/DialogsContainer.js @@ -1,20 +1,17 @@ -import React from 'react'; +import React, { lazy } from 'react'; + import AccountFormDialog from 'containers/Dialogs/AccountFormDialog'; -import UserFormDialog from 'containers/Dialogs/UserFormDialog'; -import ItemCategoryDialog from 'containers/Dialogs/ItemCategoryDialog'; -import CurrencyDialog from 'containers/Dialogs/CurrencyDialog'; -import InviteUserDialog from 'containers/Dialogs/InviteUserDialog'; -import ExchangeRateDialog from 'containers/Dialogs/ExchangeRateDialog'; +// import UserFormDialog from 'containers/Dialogs/UserFormDialog'; +// import ItemCategoryDialog from 'containers/Dialogs/ItemCategoryDialog'; +// import CurrencyDialog from 'containers/Dialogs/CurrencyDialog'; +// import InviteUserDialog from 'containers/Dialogs/InviteUserDialog'; +// import ExchangeRateDialog from 'containers/Dialogs/ExchangeRateDialog'; + export default function DialogsContainer() { return (
- - {/* */} - - - - {/* */} +
); } diff --git a/client/src/components/DynamicFilter/DynamicFilterCompatators.js b/client/src/components/DynamicFilter/DynamicFilterCompatators.js index 1782c4387..9eb5573ea 100644 --- a/client/src/components/DynamicFilter/DynamicFilterCompatators.js +++ b/client/src/components/DynamicFilter/DynamicFilterCompatators.js @@ -1,5 +1,6 @@ export const BooleanCompatators = [ { value: 'is', label_id: 'is' }, + { value: 'is_not', label_id: 'is_not' }, ]; export const TextCompatators = [ @@ -20,6 +21,15 @@ export const OptionsCompatators = [ { 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) => { return [ ...(dataType === 'options' @@ -27,7 +37,9 @@ export const getConditionTypeCompatators = (dataType) => { : dataType === 'date' ? [...DateCompatators] : dataType === 'boolean' - ? [...BooleanCompatators] + ? [...BooleanCompatators] + : dataType === 'number' + ? [...NumberCampatators] : [...TextCompatators]), ]; }; diff --git a/client/src/components/DynamicFilter/DynamicFilterValueField.js b/client/src/components/DynamicFilter/DynamicFilterValueField.js index a30bcb87c..241465ec1 100644 --- a/client/src/components/DynamicFilter/DynamicFilterValueField.js +++ b/client/src/components/DynamicFilter/DynamicFilterValueField.js @@ -14,72 +14,80 @@ import { FormattedMessage as T, useIntl } from 'react-intl'; import { debounce } from 'lodash'; import moment from 'moment'; -import { If, Choose, ListSelect, MODIFIER } from 'components'; +import { Choose, ListSelect, MODIFIER } from 'components'; import withResourceDetail from 'containers/Resources/withResourceDetails'; import withResourceActions from 'containers/Resources/withResourcesActions'; -import { - getConditionTypeCompatators, - getConditionDefaultCompatator, -} from './DynamicFilterCompatators'; - import { compose, momentFormatter } from 'utils'; /** * Dynamic filter fields. */ function DynamicFilterValueField({ - dataType, - value, - - initialValue, - error, - // fieldkey, - // resourceKey, // #withResourceDetail resourceName, - resourceData, + resourceData = [], requestResourceData, + // #ownProps + fieldType, + fieldName, + value, + initialValue, + error, + optionsResource, + optionsKey = 'key', + optionsLabel = 'label', + options, onChange, - rosourceKey, - - inputDebounceWait = 500, + inputDebounceWait = 250, }) { const { formatMessage } = useIntl(); const [localValue, setLocalValue] = useState(); - const fetchResourceData = useQuery( - ['resource-data', resourceName && resourceName], - (k, resName) => requestResourceData(resName), - { manual: true }, - ); - + // Makes `localValue` controlled mode from `value`. useEffect(() => { if (value !== localValue) { setLocalValue(value); } }, [value]); + // Fetches resource data. + const fetchResourceData = useQuery( + ['resource-data', resourceName], + (key, _resourceName) => requestResourceData(_resourceName), + { + enabled: resourceName, + }, + ); + // Account type item of select filed. const menuItem = (item, { handleClick, modifiers, query }) => { - return ; + return (); }; + // Handle list button click. const handleBtnClick = () => { - fetchResourceData.refetch({}); + }; - const listOptions = useMemo(() => Object.values(resourceData), [ - resourceData, + const listOptions = useMemo(() => [ + ...(resourceData || []), + ...(options || []), + ], [ + resourceData, options, ]); // Filters accounts types items. const filterItems = (query, item, _index, exactMatch) => { - const normalizedTitle = item.name.toLowerCase(); + const normalizedTitle = item.label.toLowerCase(); const normalizedQuery = query.toLowerCase(); if (exactMatch) { @@ -89,16 +97,16 @@ function DynamicFilterValueField({ } }; + // Handle list item selected. const onItemSelect = (item) => { - onChange && onChange(item); + onChange && onChange(item[optionsKey]); }; const handleInputChangeThrottled = useRef( - debounce((value) => { - onChange && onChange(value); - }, inputDebounceWait), + debounce((value) => { onChange && onChange(value); }, inputDebounceWait), ); + // Handle input change. const handleInputChange = (e) => { if (e.currentTarget.type === 'checkbox') { setLocalValue(e.currentTarget.checked); @@ -108,12 +116,14 @@ function DynamicFilterValueField({ handleInputChangeThrottled.current(e.currentTarget.value); }; + // Handle checkbox field change. const handleCheckboxChange = (e) => { const value = !!e.currentTarget.checked; setLocalValue(value); onChange && onChange(value); } + // Handle date field change. const handleDateChange = (date) => { setLocalValue(date); onChange && onChange(date); @@ -126,7 +136,7 @@ function DynamicFilterValueField({ return ( - + } - labelProp={'name'} - buttonProps={{ onClick: handleBtnClick }} + selectedItemProp={optionsKey} + defaultText={`Select an option`} + labelProp={optionsLabel} + buttonProps={{ + onClick: handleBtnClick + }} /> - + - + @@ -184,7 +196,7 @@ function DynamicFilterValueField({ } const mapStateToProps = (state, props) => ({ - resourceName: props.dataResource, + resourceName: props.optionsResource, }); const withResourceFilterValueField = connect(mapStateToProps); diff --git a/client/src/components/FilterDropdown.js b/client/src/components/FilterDropdown.js index 0c4a8903c..04f387972 100644 --- a/client/src/components/FilterDropdown.js +++ b/client/src/components/FilterDropdown.js @@ -1,5 +1,5 @@ -// @flow -import React, { useEffect, useMemo, useCallback, useRef } from 'react'; +// @flow +import React, { useEffect, useMemo, useCallback, useState } from 'react'; import { FormGroup, Classes, @@ -10,7 +10,6 @@ import { import { useFormik } from 'formik'; import { isEqual, last } from 'lodash'; import { usePrevious } from 'react-use'; -import { debounce } from 'lodash'; import Icon from 'components/Icon'; import { checkRequiredProperties, uniqueMultiProps } from 'utils'; import { FormattedMessage as T, useIntl } from 'react-intl'; @@ -22,7 +21,7 @@ import Toaster from 'components/AppToaster'; import moment from 'moment'; import { getConditionTypeCompatators, - getConditionDefaultCompatator + getConditionDefaultCompatator, } from './DynamicFilter/DynamicFilterCompatators'; let limitToast; @@ -39,224 +38,238 @@ type InitialCondition = { export default function FilterDropdown({ fields, onFilterChange, - refetchDebounceWait = 10, 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 (
+ {values.conditions.map((condition, index) => ( +
+ + 1} + {...fieldProps('condition', index)} + /> + + + + + + + + + + + +
+ ))} +
+ +
); - - // 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 ( - //
- //
- // {values.conditions.map((condition, index) => ( - //
- // - // 1} - // {...fieldProps('condition', index)} - // /> - // - - // - // - // - - // - // - // - - // - - //
- // ))} - //
- - // - //
- // ); } diff --git a/client/src/components/Forms/InputPrepend.js b/client/src/components/Forms/InputPrepend.js new file mode 100644 index 000000000..f962e221b --- /dev/null +++ b/client/src/components/Forms/InputPrepend.js @@ -0,0 +1,9 @@ +import React from 'react'; + +export default function InputPrepend({ children }) { + return ( +
+ { children } +
+ ); +} \ No newline at end of file diff --git a/client/src/components/Forms/InputPrependOptions.js b/client/src/components/Forms/InputPrependOptions.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/components/NProgress/Bar.js b/client/src/components/NProgress/Bar.js index 6bd2f1730..a4a7cf4dd 100644 --- a/client/src/components/NProgress/Bar.js +++ b/client/src/components/NProgress/Bar.js @@ -5,7 +5,7 @@ const Bar = ({ progress, animationDuration }) => (
{ openDialog('account-form', {}); @@ -54,9 +59,10 @@ function AccountsActionsBar({ const filterDropdown = FilterDropdown({ fields: resourceFields, + initialConditions: accountsTableQuery.filter_roles, initialCondition: { fieldKey: 'name', - compatator: 'contains', + comparator: 'contains', value: '', }, onFilterChange: (filterConditions) => { @@ -171,8 +177,9 @@ const withAccountsActionsBar = connect(mapStateToProps); export default compose( withAccountsActionsBar, withDialogActions, - withAccounts(({ accountsViews }) => ({ + withAccounts(({ accountsViews, accountsTableQuery }) => ({ accountsViews, + accountsTableQuery, })), withResourceDetail(({ resourceFields }) => ({ resourceFields, diff --git a/client/src/containers/Accounts/AccountsDataTable.js b/client/src/containers/Accounts/AccountsDataTable.js index c1f73689f..0d8c1de4b 100644 --- a/client/src/containers/Accounts/AccountsDataTable.js +++ b/client/src/containers/Accounts/AccountsDataTable.js @@ -51,11 +51,10 @@ function NormalCell({ cell }) { function BalanceCell({ cell }) { const account = cell.row.original; - const { balance = null } = account; - return balance ? ( + return (account.amount) ? ( - + ) : ( @@ -245,7 +244,7 @@ function AccountsDataTable({ { id: 'balance', Header: formatMessage({ id: 'balance' }), - accessor: 'balance', + accessor: 'amount', Cell: BalanceCell, width: 150, }, @@ -268,11 +267,7 @@ function AccountsDataTable({ ); const selectionColumn = useMemo( - () => ({ - minWidth: 40, - width: 40, - maxWidth: 40, - }), + () => ({ minWidth: 45, width: 45, maxWidth: 45 }), [], ); diff --git a/client/src/containers/Customers/CustomerForm.js b/client/src/containers/Customers/CustomerForm.js index 9157d100b..056db476c 100644 --- a/client/src/containers/Customers/CustomerForm.js +++ b/client/src/containers/Customers/CustomerForm.js @@ -32,13 +32,13 @@ function CustomerForm({ // #withDashboardActions changePageTitle, - //#withCustomers + // #withCustomers customers, - //#withCustomerDetail + // #withCustomerDetail customer, - //#withCustomersActions + // #withCustomersActions requestSubmitCustomer, requestFetchCustomers, requestEditCustomer, @@ -47,7 +47,7 @@ function CustomerForm({ requestSubmitMedia, requestDeleteMedia, - //#Props + // #Props onFormSubmit, onCancelForm, }) { diff --git a/client/src/containers/Customers/CustomerTypeRadioField.js b/client/src/containers/Customers/CustomerTypeRadioField.js index 601491d92..8b65c3580 100644 --- a/client/src/containers/Customers/CustomerTypeRadioField.js +++ b/client/src/containers/Customers/CustomerTypeRadioField.js @@ -6,6 +6,7 @@ import { RadioGroup, Radio } from '@blueprintjs/core'; export default function RadioCustomer(props) { const { onChange, ...rest } = props; const { formatMessage } = useIntl(); + return ( ({ - 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, -); diff --git a/client/src/containers/Dialogs/AccountFormDialog.js b/client/src/containers/Dialogs/AccountFormDialog.js index 542f28ca1..b7f6b9a54 100644 --- a/client/src/containers/Dialogs/AccountFormDialog.js +++ b/client/src/containers/Dialogs/AccountFormDialog.js @@ -1,405 +1,45 @@ -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, - Dialog, - AppToaster, - FieldRequiredHint, - Hint, - AccountsSelectList, - AccountsTypesSelect, -} from 'components'; -import AccountFormDialogContainer from 'containers/Dialogs/AccountFormDialog.container'; +import React, { lazy } from 'react'; +import { FormattedMessage as T } from 'react-intl'; +import { Dialog, DialogSuspense } from 'components'; +import withDialogRedux from 'components/DialogReduxConnect'; +import { compose } from 'utils'; + +const AccountFormDialogContent = lazy(() => import('./AccountFormDialogContent')); /** * Account form dialog. */ function AccountFormDialog({ dialogName, - payload = { action: 'new', id: null }, + payload = { action: '', id: null }, 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 ( - ) : ( - - ) + (payload.action === 'edit') ? + () : + () } - className={{ - 'dialog--loading': isFetching, - 'dialog--account-form': true, - }} + className={'dialog--account-form'} autoFocus={true} canEscapeKeyClose={true} - onClosed={onDialogClosed} - onOpening={onDialogOpening} isOpen={isOpen} - isLoading={isFetching} - onClose={handleClose} > -
-
- } - labelInfo={} - className={classNames( - 'form-group--account-type', - 'form-group--select-list', - Classes.FILL, - )} - inline={true} - helperText={ - - } - intent={ - errors.account_type_id && touched.account_type_id && Intent.DANGER - } - > - } - onTypeSelected={onChangeAccountType} - buttonProps={{ disabled: payload.action === 'edit' }} - popoverProps={{ minimal: true }} - /> - - - } - labelInfo={} - className={'form-group--account-name'} - intent={errors.name && touched.name && Intent.DANGER} - helperText={} - inline={true} - > - - - - } - className={'form-group--account-code'} - intent={errors.code && touched.code && Intent.DANGER} - helperText={} - inline={true} - labelInfo={} />} - > - - - - - - - - - } - {...getFieldProps('subaccount')} - checked={values.subaccount} - /> - - - - } - className={classNames( - 'form-group--parent-account', - 'form-group--select-list', - Classes.FILL, - )} - inline={true} - > - } - selectedAccountId={values.parent_account_id} - /> - - - - } - className={'form-group--description'} - intent={errors.description && Intent.DANGER} - helperText={errors.description && errors.credential} - inline={true} - > -