BC-5 fix: general tab of preferences form submitting.

This commit is contained in:
a.bouhuolia
2021-09-04 18:49:01 +02:00
parent d6d6fefd1f
commit 11df54d4ed
25 changed files with 251 additions and 131 deletions

View File

@@ -19,7 +19,6 @@ import { Icon, Hint, If } from 'components';
import withUniversalSearchActions from 'containers/UniversalSearch/withUniversalSearchActions'; import withUniversalSearchActions from 'containers/UniversalSearch/withUniversalSearchActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withDashboard from 'containers/Dashboard/withDashboard'; import withDashboard from 'containers/Dashboard/withDashboard';
import withSettings from 'containers/Settings/withSettings';
import QuickNewDropdown from 'containers/QuickNewDropdown/QuickNewDropdown'; import QuickNewDropdown from 'containers/QuickNewDropdown/QuickNewDropdown';
import { compose } from 'utils'; import { compose } from 'utils';
@@ -76,9 +75,6 @@ function DashboardTopbar({
// #withDashboard // #withDashboard
sidebarExpended, sidebarExpended,
// #withSettings
organizationName,
// #withGlobalSearch // #withGlobalSearch
openGlobalSearch, openGlobalSearch,
@@ -190,9 +186,6 @@ export default compose(
sidebarExpended, sidebarExpended,
pageHint, pageHint,
})), })),
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
})),
withDashboardActions, withDashboardActions,
withSubscriptions( withSubscriptions(
({ isSubscriptionActive, isSubscriptionInactive }) => ({ ({ isSubscriptionActive, isSubscriptionInactive }) => ({

View File

@@ -2,8 +2,8 @@ import React from 'react';
import { Button, Popover, Menu, Position } from '@blueprintjs/core'; import { Button, Popover, Menu, Position } from '@blueprintjs/core';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import { useAuthUser } from 'hooks/state'; import { useAuthUser } from 'hooks/state';
import withSettings from 'containers/Settings/withSettings';
import { compose, firstLettersArgs } from 'utils'; import { compose, firstLettersArgs } from 'utils';
import withCurrentOrganization from '../../containers/Organization/withCurrentOrganization';
// Popover modifiers. // Popover modifiers.
const POPOVER_MODIFIERS = { const POPOVER_MODIFIERS = {
@@ -14,8 +14,8 @@ const POPOVER_MODIFIERS = {
* Sideabr head. * Sideabr head.
*/ */
function SidebarHead({ function SidebarHead({
// #withSettings // #withCurrentOrganization
organizationName, organization,
}) { }) {
const user = useAuthUser(); const user = useAuthUser();
@@ -29,9 +29,9 @@ function SidebarHead({
<Menu className={'menu--dashboard-organization'}> <Menu className={'menu--dashboard-organization'}>
<div class="org-item"> <div class="org-item">
<div class="org-item__logo"> <div class="org-item__logo">
{firstLettersArgs(...organizationName.split(' '))}{' '} {firstLettersArgs(...(organization.name || '').split(' '))}{' '}
</div> </div>
<div class="org-item__name">{organizationName}</div> <div class="org-item__name">{organization.name}</div>
</div> </div>
</Menu> </Menu>
} }
@@ -42,7 +42,7 @@ function SidebarHead({
className="title" className="title"
rightIcon={<Icon icon={'caret-down-16'} size={16} />} rightIcon={<Icon icon={'caret-down-16'} size={16} />}
> >
{organizationName} {organization.name}
</Button> </Button>
</Popover> </Popover>
<span class="subtitle">{user.full_name}</span> <span class="subtitle">{user.full_name}</span>
@@ -61,7 +61,5 @@ function SidebarHead({
} }
export default compose( export default compose(
withSettings(({ organizationSettings }) => ({ withCurrentOrganization(({ organization }) => ({ organization })),
organizationName: organizationSettings.name,
})),
)(SidebarHead); )(SidebarHead);

View File

@@ -2,14 +2,25 @@ import React, { useEffect } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider'; import DashboardInsider from 'components/Dashboard/DashboardInsider';
import HomepageContent from './HomepageContent'; import HomepageContent from './HomepageContent';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withSettings from 'containers/Settings/withSettings'; import withCurrentOrganization from '../Organization/withCurrentOrganization';
import { compose } from 'utils'; import { compose } from 'utils';
function DashboardHomepage({ changePageTitle, name }) { /**
* Dashboard homepage.
*/
function DashboardHomepage({
// #withDashboardActions
changePageTitle,
// #withCurrentOrganization
organization,
}) {
useEffect(() => { useEffect(() => {
changePageTitle(name); changePageTitle(organization.name);
}, [name, changePageTitle]); }, [organization.name, changePageTitle]);
return ( return (
<DashboardInsider name="homepage"> <DashboardInsider name="homepage">
@@ -20,7 +31,5 @@ function DashboardHomepage({ changePageTitle, name }) {
export default compose( export default compose(
withDashboardActions, withDashboardActions,
withSettings(({ organizationSettings }) => ({ withCurrentOrganization(({ organization }) => ({ organization })),
name: organizationSettings.name,
})),
)(DashboardHomepage); )(DashboardHomepage);

View File

@@ -1,12 +1,16 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getCurrentOrganizationFactory } from '../../store/authentication/authentication.selectors';
export default (mapState) => { export default (mapState) => {
const getCurrentOrganization = getCurrentOrganizationFactory();
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
const mapped = { const mapped = {
organizationTenantId: state.authentication.organizationId, organizationTenantId: state.authentication.organizationId,
organizationId: state.authentication.organization, organizationId: state.authentication.organization,
organization: getCurrentOrganization(state, props),
}; };
return (mapState) ? mapState(mapped, state, props) : mapped; return mapState ? mapState(mapped, state, props) : mapped;
}; };
return connect(mapStateToProps); return connect(mapStateToProps);
}; };

View File

@@ -5,9 +5,6 @@ const Schema = Yup.object().shape({
name: Yup.string() name: Yup.string()
.required() .required()
.label(intl.get('organization_name_')), .label(intl.get('organization_name_')),
financial_date_start: Yup.date()
.required()
.label(intl.get('date_start_')),
industry: Yup.string() industry: Yup.string()
.nullable() .nullable()
.label(intl.get('organization_industry_')), .label(intl.get('organization_industry_')),
@@ -23,7 +20,7 @@ const Schema = Yup.object().shape({
language: Yup.string() language: Yup.string()
.required() .required()
.label(intl.get('language')), .label(intl.get('language')),
time_zone: Yup.string() timezone: Yup.string()
.required() .required()
.label(intl.get('time_zone_')), .label(intl.get('time_zone_')),
date_format: Yup.string() date_format: Yup.string()

View File

@@ -1,42 +1,35 @@
import { Form } from 'formik'; import { Form } from 'formik';
import React from 'react'; import React from 'react';
import { import { Button, FormGroup, InputGroup, Intent } from '@blueprintjs/core';
Button,
FormGroup,
InputGroup,
Intent,
Position,
} from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import { TimezonePicker } from '@blueprintjs/timezone'; import { TimezonePicker } from '@blueprintjs/timezone';
import { ErrorMessage, FastField } from 'formik'; import { ErrorMessage, FastField } from 'formik';
import { DateInput } from '@blueprintjs/datetime';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { FormattedMessage as T } from 'components'; import { FormattedMessage as T } from 'components';
import { ListSelect, FieldRequiredHint } from 'components'; import { ListSelect, FieldRequiredHint } from 'components';
import { import { inputIntent } from 'utils';
inputIntent,
momentFormatter,
tansformDateValue,
handleDateChange,
} from 'utils';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { getCountries } from 'common/countries'; import { getCountries } from 'common/countries';
import { getCurrencies } from 'common/currencies'; import { getAllCurrenciesOptions } from 'common/currencies';
import { getFiscalYear } from 'common/fiscalYearOptions'; import { getFiscalYear } from 'common/fiscalYearOptions';
import { getLanguages } from 'common/languagesOptions'; import { getLanguages } from 'common/languagesOptions';
import { getDateFormats } from 'common/dateFormatsOptions'; import { useGeneralFormContext } from './GeneralFormProvider';
export default function PreferencesGeneralForm({}) { /**
* Preferences general form.
*/
export default function PreferencesGeneralForm({ isSubmitting }) {
const history = useHistory(); const history = useHistory();
const FiscalYear = getFiscalYear(); const FiscalYear = getFiscalYear();
const Countries = getCountries(); const Countries = getCountries();
const Languages = getLanguages(); const Languages = getLanguages();
const Currencies = getCurrencies(); const Currencies = getAllCurrenciesOptions();
const DataFormats = getDateFormats();
const { dateFormats } = useGeneralFormContext();
// Handle close click.
const handleCloseClick = () => { const handleCloseClick = () => {
history.go(-1); history.go(-1);
}; };
@@ -59,29 +52,7 @@ export default function PreferencesGeneralForm({}) {
)} )}
</FastField> </FastField>
{/* ---------- Financial starting date ---------- */} {/* ---------- Industry ---------- */}
<FastField name={'financial_date_start'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'financial_starting_date'} />}
labelInfo={<FieldRequiredHint />}
inline={true}
intent={inputIntent({ error, touched })}
className={classNames('form-group--select-list', CLASSES.FILL)}
helperText={<T id={'for_reporting_you_can_specify_any_month'} />}
>
<DateInput
{...momentFormatter('MMMM Do YYYY')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('financial_date_start', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
/>
</FormGroup>
)}
</FastField>
<FastField name={'industry'}> <FastField name={'industry'}>
{({ field, meta: { error, touched } }) => ( {({ field, meta: { error, touched } }) => (
<FormGroup <FormGroup
@@ -147,10 +118,10 @@ export default function PreferencesGeneralForm({}) {
form.setFieldValue('base_currency', currency.code); form.setFieldValue('base_currency', currency.code);
}} }}
selectedItem={value} selectedItem={value}
selectedItemProp={'code'} selectedItemProp={'key'}
defaultText={<T id={'select_base_currency'} />} defaultText={<T id={'select_base_currency'} />}
textProp={'name'} textProp={'name'}
labelProp={'code'} labelProp={'key'}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FormGroup>
@@ -165,8 +136,8 @@ export default function PreferencesGeneralForm({}) {
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
className={classNames('form-group--fiscal-year', CLASSES.FILL)} className={classNames('form-group--fiscal-year', CLASSES.FILL)}
inline={true} inline={true}
helperText={<ErrorMessage name="fiscal_year" />}
intent={inputIntent({ error, touched })} intent={inputIntent({ error, touched })}
helperText={<T id={'for_reporting_you_can_specify_any_month'} />}
> >
<ListSelect <ListSelect
items={FiscalYear} items={FiscalYear}
@@ -174,7 +145,7 @@ export default function PreferencesGeneralForm({}) {
form.setFieldValue('fiscal_year', value) form.setFieldValue('fiscal_year', value)
} }
selectedItem={value} selectedItem={value}
selectedItemProp={'value'} selectedItemProp={'key'}
defaultText={<T id={'select_fiscal_year'} />} defaultText={<T id={'select_fiscal_year'} />}
textProp={'name'} textProp={'name'}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
@@ -210,7 +181,7 @@ export default function PreferencesGeneralForm({}) {
</FastField> </FastField>
{/* ---------- Time zone ---------- */} {/* ---------- Time zone ---------- */}
<FastField name={'time_zone'}> <FastField name={'timezone'}>
{({ form, field: { value }, meta: { error, touched } }) => ( {({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'time_zone'} />} label={<T id={'time_zone'} />}
@@ -222,12 +193,12 @@ export default function PreferencesGeneralForm({}) {
CLASSES.FILL, CLASSES.FILL,
)} )}
intent={inputIntent({ error, touched })} intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="time_zone" />} helperText={<ErrorMessage name="timezone" />}
> >
<TimezonePicker <TimezonePicker
value={value} value={value}
onChange={(timezone) => { onChange={(timezone) => {
form.setFieldValue('time_zone', timezone); form.setFieldValue('timezone', timezone);
}} }}
valueDisplayFormat="composite" valueDisplayFormat="composite"
placeholder={<T id={'select_time_zone'} />} placeholder={<T id={'select_time_zone'} />}
@@ -248,15 +219,14 @@ export default function PreferencesGeneralForm({}) {
helperText={<ErrorMessage name="date_format" />} helperText={<ErrorMessage name="date_format" />}
> >
<ListSelect <ListSelect
items={DataFormats} items={dateFormats}
onItemSelect={(dateFormat) => { onItemSelect={(dateFormat) => {
form.setFieldValue('date_format', dateFormat.value); form.setFieldValue('date_format', dateFormat.key);
}} }}
selectedItem={value} selectedItem={value}
selectedItemProp={'value'} selectedItemProp={'key'}
defaultText={<T id={'select_date_format'} />} defaultText={<T id={'select_date_format'} />}
textProp={'name'} textProp={'label'}
labelProp={'label'}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FormGroup>
@@ -264,7 +234,7 @@ export default function PreferencesGeneralForm({}) {
</FastField> </FastField>
<div className={'card__footer'}> <div className={'card__footer'}>
<Button intent={Intent.PRIMARY} type="submit"> <Button loading={isSubmitting} intent={Intent.PRIMARY} type="submit">
<T id={'save'} /> <T id={'save'} />
</Button> </Button>
<Button onClick={handleCloseClick}> <Button onClick={handleCloseClick}>

View File

@@ -1,53 +1,53 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Formik } from 'formik'; import { Formik } from 'formik';
import { mapKeys, snakeCase } from 'lodash';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import 'style/pages/Preferences/GeneralForm.scss';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import GeneralForm from './GeneralForm'; import GeneralForm from './GeneralForm';
import { PreferencesGeneralSchema } from './General.schema'; import { PreferencesGeneralSchema } from './General.schema';
import { useGeneralFormContext } from './GeneralFormProvider'; import { useGeneralFormContext } from './GeneralFormProvider';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withSettings from 'containers/Settings/withSettings';
import { compose, optionsMapToArray } from 'utils'; import { compose } from 'utils';
import { transformToForm } from '../../../utils';
import 'style/pages/Preferences/GeneralForm.scss'; const defaultValues = {
name: '',
industry: '',
location: '',
base_currency: '',
language: '',
fiscal_year: '',
date_format: '',
timezone: '',
};
/** /**
* Preferences - General form Page. * Preferences - General form Page.
*/ */
function GeneralFormPage({ function GeneralFormPage({
// #withSettings // #withDashboardActions
organizationSettings,
//# withDashboardActions
changePreferencesPageTitle, changePreferencesPageTitle,
}) { }) {
const { updateOrganization, organization } = useGeneralFormContext();
const { saveSettingMutate } = useGeneralFormContext();
useEffect(() => { useEffect(() => {
changePreferencesPageTitle(intl.get('general')); changePreferencesPageTitle(intl.get('general'));
}, [changePreferencesPageTitle]); }, [changePreferencesPageTitle]);
function transformGeneralSettings(data) { // Initial values.
return mapKeys(data, (value, key) => snakeCase(key));
}
const initialValues = { const initialValues = {
...transformGeneralSettings(organizationSettings), ...transformToForm(organization.metadata, defaultValues),
}; };
const handleFormSubmit = (values, { setSubmitting, resetForm }) => { const handleFormSubmit = (values, { setSubmitting, resetForm }) => {
const options = optionsMapToArray(values).map((option) => {
return { key: option.key, ...option, group: 'organization' };
});
// Handle request success. // Handle request success.
const onSuccess = (response) => { const onSuccess = (response) => {
AppToaster.show({ AppToaster.show({
message: 'The general preferences has been saved.', message: intl.get('preferences.general.success_message'),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
setSubmitting(false); setSubmitting(false);
@@ -57,7 +57,9 @@ function GeneralFormPage({
const onError = (errors) => { const onError = (errors) => {
setSubmitting(false); setSubmitting(false);
}; };
saveSettingMutate({ options }).then(onSuccess).catch(onError); updateOrganization({ ...values })
.then(onSuccess)
.catch(onError);
}; };
return ( return (
@@ -70,7 +72,4 @@ function GeneralFormPage({
); );
} }
export default compose( export default compose(withDashboardActions)(GeneralFormPage);
withSettings(({ organizationSettings }) => ({ organizationSettings })),
withDashboardActions,
)(GeneralFormPage);

View File

@@ -1,7 +1,11 @@
import React, { createContext } from 'react'; import React, { createContext } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { useSaveSettings, useSettings } from 'hooks/query'; import {
useCurrentOrganization,
useUpdateOrganization,
useDateFormats,
} from 'hooks/query';
import PreferencesPageLoader from '../PreferencesPageLoader'; import PreferencesPageLoader from '../PreferencesPageLoader';
const GeneralFormContext = createContext(); const GeneralFormContext = createContext();
@@ -10,20 +14,25 @@ const GeneralFormContext = createContext();
* General form provider. * General form provider.
*/ */
function GeneralFormProvider({ ...props }) { function GeneralFormProvider({ ...props }) {
// Fetches Organization Settings. // Fetches current organization information.
const { isLoading: isSettingsLoading } = useSettings(); const { isLoading: isOrganizationLoading, data: organization } =
useCurrentOrganization();
// Save Organization Settings. const { data: dateFormats, isLoading: isDateFormatsLoading } =
const { mutateAsync: saveSettingMutate } = useSaveSettings(); useDateFormats();
// Mutate organization information.
const { mutateAsync: updateOrganization } = useUpdateOrganization();
// Provider state. // Provider state.
const provider = { const provider = {
isSettingsLoading, isOrganizationLoading,
saveSettingMutate, isDateFormatsLoading,
updateOrganization,
organization,
dateFormats,
}; };
const loading = isSettingsLoading;
return ( return (
<div <div
className={classNames( className={classNames(
@@ -32,7 +41,7 @@ function GeneralFormProvider({ ...props }) {
)} )}
> >
<div className={classNames(CLASSES.CARD)}> <div className={classNames(CLASSES.CARD)}>
{loading ? ( {isOrganizationLoading || isDateFormatsLoading ? (
<PreferencesPageLoader /> <PreferencesPageLoader />
) : ( ) : (
<GeneralFormContext.Provider value={provider} {...props} /> <GeneralFormContext.Provider value={provider} {...props} />

View File

@@ -4,7 +4,7 @@ import intl from 'react-intl-universal';
// Retrieve the setup organization form validation. // Retrieve the setup organization form validation.
export const getSetupOrganizationValidation = () => export const getSetupOrganizationValidation = () =>
Yup.object().shape({ Yup.object().shape({
organizationName: Yup.string() name: Yup.string()
.required() .required()
.label(intl.get('organization_name_')), .label(intl.get('organization_name_')),
location: Yup.string() location: Yup.string()

View File

@@ -36,13 +36,13 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
</h3> </h3>
{/* ---------- Organization name ---------- */} {/* ---------- Organization name ---------- */}
<FastField name={'organizationName'}> <FastField name={'name'}>
{({ form, field, meta: { error, touched } }) => ( {({ form, field, meta: { error, touched } }) => (
<FormGroup <FormGroup
label={<T id={'legal_organization_name'} />} label={<T id={'legal_organization_name'} />}
className={'form-group--name'} className={'form-group--name'}
intent={inputIntent({ error, touched })} intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'organizationName'} />} helperText={<ErrorMessage name={'name'} />}
> >
<InputGroup {...field} intent={inputIntent({ error, touched })} /> <InputGroup {...field} intent={inputIntent({ error, touched })} />
</FormGroup> </FormGroup>

View File

@@ -14,7 +14,7 @@ import { getSetupOrganizationValidation } from './SetupOrganization.schema';
// Initial values. // Initial values.
const defaultValues = { const defaultValues = {
organizationName: '', name: '',
location: 'libya', location: 'libya',
baseCurrency: '', baseCurrency: '',
language: 'en', language: 'en',

View File

@@ -27,3 +27,4 @@ export * from './landedCost';
export * from './UniversalSearch/UniversalSearch'; export * from './UniversalSearch/UniversalSearch';
export * from './GenericResource'; export * from './GenericResource';
export * from './jobs'; export * from './jobs';
export * from './misc';

View File

@@ -0,0 +1,16 @@
import { useRequestQuery } from '../useQueryRequest';
/**
* Retrieve the job metadata.
*/
export function useDateFormats(props = {}) {
return useRequestQuery(
['DATE_FORMATS'],
{ method: 'get', url: `/date_formats` },
{
select: (res) => res.data.data,
defaultData: [],
...props,
},
);
}

View File

@@ -72,3 +72,22 @@ export function useOrganizationSetup() {
}, },
); );
} }
/**
* Saves the settings.
*/
export function useUpdateOrganization(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
(information) => apiRequest.put('organization', information),
{
onSuccess: () => {
queryClient.invalidateQueries(t.ORGANIZATION_CURRENT);
queryClient.invalidateQueries(t.ORGANIZATIONS);
},
...props,
},
);
}

View File

@@ -1249,5 +1249,6 @@
"billing.suspend_message.description": "Your account has been suspended due to the expiration of the subscription period. Please renew the subscription to activate the account.", "billing.suspend_message.description": "Your account has been suspended due to the expiration of the subscription period. Please renew the subscription to activate the account.",
"dashboard.subscription_msg.period_over": "Subscription period is over", "dashboard.subscription_msg.period_over": "Subscription period is over",
"inventory_adjustment.details_drawer.title": "Inventory adjustment details", "inventory_adjustment.details_drawer.title": "Inventory adjustment details",
"setup.organization.location": "Location" "setup.organization.location": "Location",
"preferences.general.success_message": "The general preferences has been saved."
} }

View File

@@ -0,0 +1,24 @@
import { defaultTo } from 'lodash';
import { createSelector } from '@reduxjs/toolkit';
const getCurrentOrganizationId = (state) => state.authentication.organization;
const getCurrentTenantId = (state) => state.authentication.organizationId;
const getOrganizationsMap = (state) => state.organizations.data;
// Retrieve organization tenant id.
export const getOrganizationTenantIdFactory = () =>
createSelector(getCurrentTenantId, (tenantId) => tenantId);
// Retrieve organization id.
export const getOrganizationIdFactory = () =>
createSelector(getCurrentOrganizationId, (tenantId) => tenantId);
// Retrieve current organization meta object.
export const getCurrentOrganizationFactory = () =>
createSelector(
getCurrentTenantId,
getOrganizationsMap,
(tenantId, organizationsMap) => {
return defaultTo(organizationsMap[tenantId], {});
},
);

View File

@@ -1,4 +1,5 @@
import { createReducer } from '@reduxjs/toolkit'; import { createReducer } from '@reduxjs/toolkit';
import { omit } from 'lodash';
import t from 'store/types'; import t from 'store/types';
const initialState = { const initialState = {
@@ -16,7 +17,8 @@ const reducer = createReducer(initialState, {
organizations.forEach((organization) => { organizations.forEach((organization) => {
_data[organization.id] = { _data[organization.id] = {
...state.data[organization.id], ...state.data[organization.id],
...organization, ...organization.metadata,
...omit(organization, ['metadata']),
}; };
_dataByOrganizationId[organization.organization_id] = organization.id; _dataByOrganizationId[organization.organization_id] = organization.id;
}); });

View File

@@ -0,0 +1,41 @@
import { Inject, Service } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import BaseController from 'api/controllers/BaseController';
import MiscService from 'services/Miscellaneous/MiscService';
import DateFormatsService from 'services/Miscellaneous/DateFormats';
@Service()
export default class MiscController extends BaseController {
@Inject()
dateFormatsService: DateFormatsService;
/**
* Express router.
*/
router() {
const router = Router();
router.get(
'/date_formats',
this.validationResult,
this.asyncMiddleware(this.dateFormats.bind(this))
);
return router;
}
/**
* Retrieve date formats options.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
dateFormats(req: Request, res: Response, next: NextFunction) {
try {
const dateFormats = this.dateFormatsService.getDateFormats();
return res.status(200).send({ data: dateFormats });
} catch (error) {
next(error);
}
}
}

View File

@@ -13,8 +13,8 @@ import {
ACCEPTED_CURRENCIES, ACCEPTED_CURRENCIES,
MONTHS, MONTHS,
ACCEPTED_LOCALES, ACCEPTED_LOCALES,
DATE_FORMATS,
} from 'services/Organization/constants'; } from 'services/Organization/constants';
import { DATE_FORMATS } from 'services/Miscellaneous/DateFormats/constants';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import BaseController from 'api/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';
@@ -64,7 +64,7 @@ export default class OrganizationController extends BaseController {
*/ */
private get buildValidationSchema(): ValidationChain[] { private get buildValidationSchema(): ValidationChain[] {
return [ return [
check('organization_name').exists().trim(), check('name').exists().trim(),
check('base_currency').exists().isIn(ACCEPTED_CURRENCIES), check('base_currency').exists().isIn(ACCEPTED_CURRENCIES),
check('timezone').exists().isIn(moment.tz.names()), check('timezone').exists().isIn(moment.tz.names()),
check('fiscal_year').exists().isIn(MONTHS), check('fiscal_year').exists().isIn(MONTHS),

View File

@@ -42,6 +42,7 @@ import Licenses from 'api/controllers/Subscription/Licenses';
import InventoryAdjustments from 'api/controllers/Inventory/InventoryAdjustments'; import InventoryAdjustments from 'api/controllers/Inventory/InventoryAdjustments';
import asyncRenderMiddleware from './middleware/AsyncRenderMiddleware'; import asyncRenderMiddleware from './middleware/AsyncRenderMiddleware';
import Jobs from './controllers/Jobs'; import Jobs from './controllers/Jobs';
import Miscellaneous from 'api/controllers/Miscellaneous';
export default () => { export default () => {
const app = Router(); const app = Router();
@@ -95,6 +96,8 @@ export default () => {
dashboard.use('/media', Container.get(Media).router()); dashboard.use('/media', Container.get(Media).router());
dashboard.use('/inventory_adjustments', Container.get(InventoryAdjustments).router()); dashboard.use('/inventory_adjustments', Container.get(InventoryAdjustments).router());
dashboard.use('/', Container.get(Miscellaneous).router());
app.use('/', dashboard); app.use('/', dashboard);
return app; return app;

View File

@@ -0,0 +1,11 @@
export const DATE_FORMATS = [
'MM/DD/YY',
'DD/MM/YY',
'YY/MM/DD',
'MM/DD/yyyy',
'DD/MM/yyyy',
'yyyy/MM/DD',
'DD MMM YYYY',
'DD MMMM YYYY',
'MMMM DD, YYYY',
];

View File

@@ -0,0 +1,15 @@
import moment from 'moment-timezone';
import { Service } from 'typedi';
import { DATE_FORMATS } from './constants';
@Service()
export default class DateFormatsService {
getDateFormats() {
return DATE_FORMATS.map((dateFormat) => {
return {
label: `${moment().format(dateFormat)} [${dateFormat}]`,
key: dateFormat,
};
});
}
}

View File

@@ -0,0 +1,8 @@
import { Service } from 'typedi';
@Service()
export default class MiscService {
getDateFormats() {
return [];
}
}

View File

@@ -1,4 +1,5 @@
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { ObjectId } from 'mongodb';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import { import {
IOrganizationBuildDTO, IOrganizationBuildDTO,
@@ -12,7 +13,6 @@ import {
import events from 'subscribers/events'; import events from 'subscribers/events';
import TenantsManager from 'services/Tenancy/TenantsManager'; import TenantsManager from 'services/Tenancy/TenantsManager';
import { Tenant } from 'system/models'; import { Tenant } from 'system/models';
import { ObjectId } from 'mongodb';
const ERRORS = { const ERRORS = {
TENANT_NOT_FOUND: 'tenant_not_found', TENANT_NOT_FOUND: 'tenant_not_found',
@@ -137,8 +137,8 @@ export default class OrganizationService {
/** /**
* Updates organization information. * Updates organization information.
* @param {ITenant} tenantId * @param {ITenant} tenantId
* @param {IOrganizationUpdateDTO} organizationDTO * @param {IOrganizationUpdateDTO} organizationDTO
*/ */
public async updateOrganization( public async updateOrganization(
tenantId: number, tenantId: number,

View File

@@ -3,8 +3,9 @@ exports.up = function (knex) {
table.bigIncrements(); table.bigIncrements();
table.integer('tenant_id').unsigned(); table.integer('tenant_id').unsigned();
table.string('organization_name'); table.string('name');
table.string('industry'); table.string('industry');
table.string('location');
table.string('base_currency'); table.string('base_currency');
table.string('language'); table.string('language');
@@ -13,7 +14,6 @@ exports.up = function (knex) {
table.string('date_format'); table.string('date_format');
table.string('fiscal_year'); table.string('fiscal_year');
table.string('financial_start_date');
}); });
}; };