diff --git a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFields.js b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFields.js index c24588e34..beb88ef24 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFields.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/CurrencyFormFields.js @@ -2,7 +2,9 @@ import React from 'react'; import { Classes, FormGroup, InputGroup } from '@blueprintjs/core'; import { FastField } from 'formik'; import { FormattedMessage as T } from 'react-intl'; +import classNames from 'classnames'; +import { CLASSES } from 'common/classes'; import { useCurrencyFormContext } from './CurrencyFormProvider'; import { ErrorMessage, FieldRequiredHint, ListSelect } from 'components'; @@ -25,7 +27,7 @@ export default function CurrencyFormFields() { field: { value }, meta: { error, touched }, }) => ( - + )} diff --git a/client/src/containers/Dialogs/CurrencyFormDialog/index.js b/client/src/containers/Dialogs/CurrencyFormDialog/index.js index 5125602b9..6a5fe2b8b 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog/index.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/index.js @@ -30,7 +30,7 @@ function CurrencyFormDialog({ isOpen={isOpen} autoFocus={true} canEscapeKeyClose={true} - style={{ width: '450px' }} + style={{ width: '400px' }} > { const options = transformToOptions(values); + setSubmitting(true); const onSuccess = () => { AppToaster.show({ @@ -68,21 +68,12 @@ function AccountantFormPage({ }; return ( -
-
- -
-
+ ); } diff --git a/client/src/containers/Preferences/Accountant/AccountantFormProvider.js b/client/src/containers/Preferences/Accountant/AccountantFormProvider.js index a104ed451..9e2305120 100644 --- a/client/src/containers/Preferences/Accountant/AccountantFormProvider.js +++ b/client/src/containers/Preferences/Accountant/AccountantFormProvider.js @@ -1,6 +1,8 @@ import React from 'react'; -import { LoadingIndicator } from 'components'; +import classNames from 'classnames'; +import { CLASSES } from 'common/classes'; import { useAccounts, useSaveSettings, useSettings } from 'hooks/query'; +import PreferencesPageLoader from '../PreferencesPageLoader'; const AccountantFormContext = React.createContext(); @@ -24,10 +26,23 @@ function AccountantFormProvider({ ...props }) { saveSettingMutate, }; + const isLoading = isSettingsLoading || isAccountsLoading; + return ( - - - +
+
+ {isLoading ? ( + + ) : ( + + )} +
+
); } diff --git a/client/src/containers/Preferences/General/GeneralForm.js b/client/src/containers/Preferences/General/GeneralForm.js index 44f6f13c8..856a9a8e5 100644 --- a/client/src/containers/Preferences/General/GeneralForm.js +++ b/client/src/containers/Preferences/General/GeneralForm.js @@ -36,220 +36,224 @@ export default function PreferencesGeneralForm({}) { return (
- - {({ field, meta: { error, touched } }) => ( - } - labelInfo={} - inline={true} - intent={inputIntent({ error, touched })} - className={'form-group--org-name'} - helperText={'Shown on sales forms and purchase orders.'} - > - - - )} - + + {({ field, meta: { error, touched } }) => ( + } + labelInfo={} + inline={true} + intent={inputIntent({ error, touched })} + className={'form-group--org-name'} + helperText={'Shown on sales forms and purchase orders.'} + > + + + )} + - - {({ form, field: { value }, meta: { error, touched } }) => ( - } - labelInfo={} - inline={true} - intent={inputIntent({ error, touched })} - className={classNames('form-group--select-list', CLASSES.FILL)} - helperText={'For reporting, you can specify any month as the start of your financial year (also called your financial reporting year or accounting year).'} - > - { - form.setFieldValue('financial_date_start', formattedDate); - })} - popoverProps={{ position: Position.BOTTOM, minimal: true }} - /> - - )} - - - - {({ field, meta: { error, touched } }) => ( - } - inline={true} - intent={inputIntent({ error, touched })} - helperText={} - className={'form-group--org-industry'} - > - - - )} - - - - {({ form, field: { value }, meta: { error, touched } }) => ( - } - className={classNames( - 'form-group--business-location', - CLASSES.FILL, - )} - inline={true} - helperText={} - intent={inputIntent({ error, touched })} - > - { - form.setFieldValue('location', value); - }} - selectedItem={value} - selectedItemProp={'value'} - defaultText={} - textProp={'name'} - popoverProps={{ minimal: true }} - /> - - )} - - - - {({ form, field: { value }, meta: { error, touched } }) => ( - } - labelInfo={} - className={classNames('form-group--base-currency', CLASSES.FILL)} - inline={true} - intent={inputIntent({ error, touched })} - helperText={"You can't change the base currency as there are transactions recorded in your organization."} - > - { - form.setFieldValue('base_currency', currency.code); - }} - selectedItem={value} - selectedItemProp={'code'} - defaultText={} - textProp={'name'} - labelProp={'code'} - popoverProps={{ minimal: true }} - /> - - )} - - - - {({ form, field: { value }, meta: { error, touched } }) => ( - } - labelInfo={} - className={classNames('form-group--fiscal-year', CLASSES.FILL)} - inline={true} - helperText={} - intent={inputIntent({ error, touched })} - > - - form.setFieldValue('fiscal_year', value) + + {({ form, field: { value }, meta: { error, touched } }) => ( + } + labelInfo={} + inline={true} + intent={inputIntent({ error, touched })} + className={classNames('form-group--select-list', CLASSES.FILL)} + helperText={ + 'For reporting, you can specify any month as the start of your financial year (also called your financial reporting year or accounting year).' } - selectedItem={value} - selectedItemProp={'value'} - defaultText={} - textProp={'name'} - popoverProps={{ minimal: true }} - /> - - )} - + > + { + form.setFieldValue('financial_date_start', formattedDate); + })} + popoverProps={{ position: Position.BOTTOM, minimal: true }} + /> + + )} + - - {({ form, field: { value }, meta: { error, touched } }) => ( - } - labelInfo={} - inline={true} - className={classNames('form-group--language', CLASSES.FILL)} - intent={inputIntent({ error, touched })} - helperText={} - > - } - selectedItem={value} - onItemSelect={(item) => - form.setFieldValue('language', item.value) + + {({ field, meta: { error, touched } }) => ( + } + inline={true} + intent={inputIntent({ error, touched })} + helperText={} + className={'form-group--org-industry'} + > + + + )} + + + + {({ form, field: { value }, meta: { error, touched } }) => ( + } + className={classNames( + 'form-group--business-location', + CLASSES.FILL, + )} + inline={true} + helperText={} + intent={inputIntent({ error, touched })} + > + { + form.setFieldValue('location', value); + }} + selectedItem={value} + selectedItemProp={'value'} + defaultText={} + textProp={'name'} + popoverProps={{ minimal: true }} + /> + + )} + + + + {({ form, field: { value }, meta: { error, touched } }) => ( + } + labelInfo={} + className={classNames('form-group--base-currency', CLASSES.FILL)} + inline={true} + intent={inputIntent({ error, touched })} + helperText={ + "You can't change the base currency as there are transactions recorded in your organization." } - popoverProps={{ minimal: true }} - /> - - )} - + > + { + form.setFieldValue('base_currency', currency.code); + }} + selectedItem={value} + selectedItemProp={'code'} + defaultText={} + textProp={'name'} + labelProp={'code'} + popoverProps={{ minimal: true }} + /> + + )} + - - {({ form, field: { value }, meta: { error, touched } }) => ( - } - labelInfo={} - inline={true} - className={classNames( - 'form-group--time-zone', - CLASSES.FORM_GROUP_LIST_SELECT, - CLASSES.FILL, - )} - intent={inputIntent({ error, touched })} - helperText={} - > - { - form.setFieldValue('time_zone', timezone); - }} - valueDisplayFormat="composite" - placeholder={} - /> - - )} - + + {({ form, field: { value }, meta: { error, touched } }) => ( + } + labelInfo={} + className={classNames('form-group--fiscal-year', CLASSES.FILL)} + inline={true} + helperText={} + intent={inputIntent({ error, touched })} + > + + form.setFieldValue('fiscal_year', value) + } + selectedItem={value} + selectedItemProp={'value'} + defaultText={} + textProp={'name'} + popoverProps={{ minimal: true }} + /> + + )} + - - {({ form, field: { value }, meta: { error, touched } }) => ( - } - labelInfo={} - inline={true} - className={classNames('form-group--date-format', CLASSES.FILL)} - intent={inputIntent({ error, touched })} - helperText={} - > - { - form.setFieldValue('date_format', dateFormat.value); - }} - selectedItem={value} - selectedItemProp={'value'} - defaultText={} - textProp={'name'} - labelProp={'label'} - popoverProps={{ minimal: true }} - /> - - )} - + + {({ form, field: { value }, meta: { error, touched } }) => ( + } + labelInfo={} + inline={true} + className={classNames('form-group--language', CLASSES.FILL)} + intent={inputIntent({ error, touched })} + helperText={} + > + } + selectedItem={value} + onItemSelect={(item) => + form.setFieldValue('language', item.value) + } + popoverProps={{ minimal: true }} + /> + + )} + -
- - -
-
+ + {({ form, field: { value }, meta: { error, touched } }) => ( + } + labelInfo={} + inline={true} + className={classNames( + 'form-group--time-zone', + CLASSES.FORM_GROUP_LIST_SELECT, + CLASSES.FILL, + )} + intent={inputIntent({ error, touched })} + helperText={} + > + { + form.setFieldValue('time_zone', timezone); + }} + valueDisplayFormat="composite" + placeholder={} + /> + + )} + + + + {({ form, field: { value }, meta: { error, touched } }) => ( + } + labelInfo={} + inline={true} + className={classNames('form-group--date-format', CLASSES.FILL)} + intent={inputIntent({ error, touched })} + helperText={} + > + { + form.setFieldValue('date_format', dateFormat.value); + }} + selectedItem={value} + selectedItemProp={'value'} + defaultText={} + textProp={'name'} + labelProp={'label'} + popoverProps={{ minimal: true }} + /> + + )} + + +
+ + +
+ ); } diff --git a/client/src/containers/Preferences/General/GeneralFormPage.js b/client/src/containers/Preferences/General/GeneralFormPage.js index a003ac049..72cd456bb 100644 --- a/client/src/containers/Preferences/General/GeneralFormPage.js +++ b/client/src/containers/Preferences/General/GeneralFormPage.js @@ -2,9 +2,7 @@ import React, { useEffect } from 'react'; import { Formik } from 'formik'; import { mapKeys, snakeCase } from 'lodash'; import { Intent } from '@blueprintjs/core'; -import classNames from 'classnames'; import { useIntl } from 'react-intl'; -import { CLASSES } from 'common/classes'; import { AppToaster } from 'components'; import GeneralForm from './GeneralForm'; @@ -46,6 +44,7 @@ function GeneralFormPage({ const options = optionsMapToArray(values).map((option) => { return { key: option.key, ...option, group: 'organization' }; }); + // Handle request success. const onSuccess = (response) => { AppToaster.show({ message: 'The general preferences has been saved.', @@ -54,6 +53,7 @@ function GeneralFormPage({ setSubmitting(false); resetForm(); }; + // Handle request error. const onError = (errors) => { setSubmitting(false); }; @@ -61,21 +61,12 @@ function GeneralFormPage({ }; return ( -
-
- -
-
+ ); } diff --git a/client/src/containers/Preferences/General/GeneralFormProvider.js b/client/src/containers/Preferences/General/GeneralFormProvider.js index 03b01c69d..a6c114f2f 100644 --- a/client/src/containers/Preferences/General/GeneralFormProvider.js +++ b/client/src/containers/Preferences/General/GeneralFormProvider.js @@ -1,6 +1,8 @@ import React, { createContext } from 'react'; -import { LoadingIndicator } from 'components'; +import classNames from 'classnames'; +import { CLASSES } from 'common/classes'; import { useSaveSettings, useSettings } from 'hooks/query'; +import PreferencesPageLoader from '../PreferencesPageLoader'; const GeneralFormContext = createContext(); @@ -8,8 +10,8 @@ const GeneralFormContext = createContext(); * General form provider. */ function GeneralFormProvider({ ...props }) { - //Fetches Organization Settings. - const { isFetching: isSettingsLoading } = useSettings(); + // Fetches Organization Settings. + const { isLoading: isSettingsLoading } = useSettings(); // Save Organization Settings. const { mutateAsync: saveSettingMutate } = useSaveSettings(); @@ -20,10 +22,23 @@ function GeneralFormProvider({ ...props }) { saveSettingMutate, }; + const loading = isSettingsLoading; + return ( - - - +
+
+ {loading ? ( + + ) : ( + + )} +
+
); } diff --git a/client/src/containers/Preferences/Item/Item.schema.js b/client/src/containers/Preferences/Item/Item.schema.js index ccb5698f2..5a032f4d1 100644 --- a/client/src/containers/Preferences/Item/Item.schema.js +++ b/client/src/containers/Preferences/Item/Item.schema.js @@ -1,9 +1,9 @@ import * as Yup from 'yup'; const Schema = Yup.object().shape({ - sell_account: Yup.number().nullable().required(), - cost_account: Yup.number().nullable().required(), - inventory_account: Yup.number().nullable().required(), + sell_account: Yup.number().nullable(), + cost_account: Yup.number().nullable(), + inventory_account: Yup.number().nullable(), }); export const ItemPreferencesSchema = Schema; diff --git a/client/src/containers/Preferences/Item/ItemFormPage.js b/client/src/containers/Preferences/Item/ItemFormPage.js index e9bbdcfc2..512d5d732 100644 --- a/client/src/containers/Preferences/Item/ItemFormPage.js +++ b/client/src/containers/Preferences/Item/ItemFormPage.js @@ -1,8 +1,6 @@ import React, { useEffect } from 'react'; import { Formik } from 'formik'; import { Intent } from '@blueprintjs/core'; -import classNames from 'classnames'; -import { CLASSES } from 'common/classes'; import { AppToaster } from 'components'; import { useIntl } from 'react-intl'; import { ItemPreferencesSchema } from './Item.schema'; @@ -19,24 +17,28 @@ import 'style/pages/Preferences/Accounting.scss'; function ItemFormPage({ // #withSettings itemsSettings, - //# withDashboardActions + + // #withDashboardActions changePreferencesPageTitle, }) { const { formatMessage } = useIntl(); const { saveSettingMutate } = useItemFormContext(); const initialValues = { + sell_account: '', + cost_account: '', + inventory_account: '', ...transformGeneralSettings(itemsSettings), }; useEffect(() => { changePreferencesPageTitle(formatMessage({ id: 'items' })); - }, [changePreferencesPageTitle]); + }, [formatMessage, changePreferencesPageTitle]); + // Handle form submit. const handleFormSubmit = (values, { setSubmitting, setErrors }) => { - const options = optionsMapToArray(values).map((option) => { - return { key: option.key, ...option, group: 'items' }; - }); + const options = optionsMapToArray(values) + .map((option) => ({ ...option, group: 'items' })); const onSuccess = () => { AppToaster.show({ @@ -55,21 +57,12 @@ function ItemFormPage({ }; return ( -
-
- -
-
+ ); } diff --git a/client/src/containers/Preferences/Item/ItemFormProvider.js b/client/src/containers/Preferences/Item/ItemFormProvider.js index a70f27c1a..81b6fa2ce 100644 --- a/client/src/containers/Preferences/Item/ItemFormProvider.js +++ b/client/src/containers/Preferences/Item/ItemFormProvider.js @@ -1,7 +1,9 @@ import React, { useContext, createContext } from 'react'; -import { LoadingIndicator } from 'components'; +import classNames from 'classnames'; +import { CLASSES } from 'common/classes'; import { useAccounts, useSaveSettings } from 'hooks/query'; +import PreferencesPageLoader from '../PreferencesPageLoader'; const ItemFormContext = createContext(); @@ -22,10 +24,23 @@ function ItemFormProvider({ ...props }) { saveSettingMutate, }; + const isLoading = isAccountsLoading; + return ( - - - +
+
+ {isLoading ? ( + + ) : ( + + )} +
+
); } diff --git a/client/src/containers/Preferences/PreferencesPageLoader.js b/client/src/containers/Preferences/PreferencesPageLoader.js new file mode 100644 index 000000000..7694058e2 --- /dev/null +++ b/client/src/containers/Preferences/PreferencesPageLoader.js @@ -0,0 +1,23 @@ +import React from 'react'; +import ContentLoader from 'react-content-loader'; + +export default function PreferencesPageLoader(props) { + return ( + + + + + + + + + ); +} diff --git a/client/src/style/pages/Currency/CurrencyFormDialog.scss b/client/src/style/pages/Currency/CurrencyFormDialog.scss index 9440a1cdf..ae1ab9d6f 100644 --- a/client/src/style/pages/Currency/CurrencyFormDialog.scss +++ b/client/src/style/pages/Currency/CurrencyFormDialog.scss @@ -1,5 +1,7 @@ .dialog--currency-form { .bp3-dialog-body { + margin-bottom: 30px; + .bp3-form-group.bp3-inline { .bp3-label { min-width: 140px; diff --git a/server/src/api/controllers/Settings.ts b/server/src/api/controllers/Settings.ts index 1760eb08f..74a5e31aa 100644 --- a/server/src/api/controllers/Settings.ts +++ b/server/src/api/controllers/Settings.ts @@ -41,7 +41,7 @@ export default class SettingsController extends BaseController { return [ body('options').isArray({ min: 1 }), body('options.*.key').exists().trim().isLength({ min: 1 }), - body('options.*.value').exists().trim().isLength({ min: 1 }), + body('options.*.value').exists().trim(), body('options.*.group').exists().trim().isLength({ min: 1 }), ]; } diff --git a/server/src/database/seeds/core/20200810121809_seed_settings.js b/server/src/database/seeds/core/20200810121809_seed_settings.js index 62e36cfd7..1eaeec22d 100644 --- a/server/src/database/seeds/core/20200810121809_seed_settings.js +++ b/server/src/database/seeds/core/20200810121809_seed_settings.js @@ -5,6 +5,12 @@ exports.up = (knex) => { const tenancyService = Container.get(TenancyService); const settings = tenancyService.settings(knex.userParams.tenantId); + // Orgnization settings. + settings.set({ group: 'organization', key: 'accounting_basis', value: 'accural' }); + + // Accounts settings. + settings.set({ group: 'accounts', key: 'account_code_unique', value: true }); + // Manual journals settings. settings.set({ group: 'manual_journals', key: 'next_number', value: '00001' }); settings.set({ group: 'manual_journals', key: 'auto_increment', value: true }); diff --git a/server/src/services/Currencies/CurrenciesService.ts b/server/src/services/Currencies/CurrenciesService.ts index adb94cfa5..76f47b218 100644 --- a/server/src/services/Currencies/CurrenciesService.ts +++ b/server/src/services/Currencies/CurrenciesService.ts @@ -261,6 +261,7 @@ export default class CurrenciesService implements ICurrenciesService { await Currency.query().insert({ currency_code: currencyMeta.code, currency_name: currencyMeta.name, + currency_sign: currencyMeta.symbol, }); } }