chrone: sperate client and server to different repos.

This commit is contained in:
a.bouhuolia
2021-09-21 17:13:53 +02:00
parent e011b2a82b
commit 18df5530c7
10015 changed files with 17686 additions and 97524 deletions

View File

@@ -0,0 +1,14 @@
import React from 'react';
import AccountantFormPage from './AccountantFormPage';
import { AccountantFormProvider } from './AccountantFormProvider';
/**
* Accountant preferences.
*/
export default function AccountantPreferences() {
return (
<AccountantFormProvider>
<AccountantFormPage />
</AccountantFormProvider>
);
}

View File

@@ -0,0 +1,12 @@
import * as Yup from 'yup';
const Schema = Yup.object().shape({
accounting_basis: Yup.string().required(),
account_code_required: Yup.boolean().nullable(),
account_code_unique: Yup.boolean().nullable(),
deposit_account: Yup.number().nullable(),
withdrawal_account: Yup.number().nullable(),
advance_deposit: Yup.number().nullable(),
});
export const AccountantSchema = Schema;

View File

@@ -0,0 +1,232 @@
import React from 'react';
import { Form, FastField, useFormikContext } from 'formik';
import {
FormGroup,
RadioGroup,
Radio,
Checkbox,
Button,
Intent,
} from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import intl from 'react-intl-universal';
import {
FormattedMessage as T,
AccountsSelectList,
FieldRequiredHint,
} from 'components';
import { handleStringChange, inputIntent } from 'utils';
import { useAccountantFormContext } from './AccountantFormProvider';
/**
* Accountant form.
*/
export default function AccountantForm() {
const history = useHistory();
const { isSubmitting } = useFormikContext();
const handleCloseClick = () => {
history.go(-1);
};
const { accounts } = useAccountantFormContext();
return (
<Form>
{/* ----------- Accounts ----------- */}
<FormGroup
label={
<strong>
<T id={'accounts'} />
</strong>
}
className={'accounts-checkbox'}
>
{/*------------ account code required -----------*/}
<FastField name={'account_code_required'} type={'checkbox'}>
{({ field }) => (
<FormGroup inline={true}>
<Checkbox
inline={true}
label={
<T
id={'make_account_code_required_when_create_a_new_accounts'}
/>
}
name={'account_code_required'}
{...field}
/>
</FormGroup>
)}
</FastField>
{/*------------ account code unique -----------*/}
<FastField name={'account_code_unique'} type={'checkbox'}>
{({ field }) => (
<FormGroup inline={true}>
<Checkbox
inline={true}
label={
<T
id={
'should_account_code_be_unique_when_create_a_new_account'
}
/>
}
name={'account_code_unique'}
{...field}
/>
</FormGroup>
)}
</FastField>
</FormGroup>
{/* ----------- Accounting basis ----------- */}
<FastField name={'accounting_basis'}>
{({
form: { setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
label={
<strong>
<T id={'accounting_basis_'} />
</strong>
}
>
<RadioGroup
inline={true}
selectedValue={value}
onChange={handleStringChange((_value) => {
setFieldValue('accounting_basis', _value);
})}
>
<Radio label={intl.get('cash')} value="cash" />
<Radio label={intl.get('accrual')} value="accrual" />
</RadioGroup>
</FormGroup>
)}
</FastField>
{/* ----------- Deposit customer account ----------- */}
<FastField name={'deposit_account'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={
<strong>
<T id={'deposit_customer_account'} />
</strong>
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_after_customer_make_payment'
}
/>
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={({ id }) => {
setFieldValue('deposit_account', id);
}}
selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />}
// filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
/>
</FormGroup>
)}
</FastField>
{/* ----------- Withdrawal vendor account ----------- */}
<FastField name={'withdrawal_account'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={
<strong>
<T id={'withdrawal_vendor_account'} />
</strong>
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_after_customer_make_payment'
}
/>
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={({ id }) => {
setFieldValue('withdrawal_account', id);
}}
selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />}
/>
</FormGroup>
)}
</FastField>
{/* ----------- Withdrawal customer account ----------- */}
<FastField name={'advance_deposit'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={
<strong>
<T id={'customer_advance_deposit'} />
</strong>
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_vendor_advanced_deposits'
}
/>
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={({ id }) => {
setFieldValue('advance_deposit', id);
}}
selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />}
// filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
/>
</FormGroup>
)}
</FastField>
<div className={'card__footer'}>
<Button intent={Intent.PRIMARY} loading={isSubmitting} type="submit">
<T id={'save'} />
</Button>
<Button disabled={isSubmitting} onClick={handleCloseClick}>
<T id={'close'} />
</Button>
</div>
</Form>
);
}

View File

@@ -0,0 +1,93 @@
import React, { useEffect } from 'react';
import { Formik } from 'formik';
import { pick } from 'lodash';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
import intl from 'react-intl-universal';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withSettings from 'containers/Settings/withSettings';
import AccountantForm from './AccountantForm';
import { AccountantSchema } from './Accountant.schema';
import { useAccountantFormContext } from './AccountantFormProvider';
import { transformToOptions } from './utils';
import { compose, transformGeneralSettings } from 'utils';
import 'style/pages/Preferences/Accounting.scss';
// Accountant preferences.
function AccountantFormPage({
//# withDashboardActions
changePreferencesPageTitle,
// #withSettings
organizationSettings,
paymentReceiveSettings,
accountsSettings,
billPaymentSettings,
}) {
const { saveSettingMutate } = useAccountantFormContext();
const accountantSettings = {
...billPaymentSettings,
...accountsSettings,
...pick(organizationSettings, ['accountingBasis']),
...pick(paymentReceiveSettings, ['depositAccount', 'advanceDeposit']),
};
const initialValues = {
...transformGeneralSettings(accountantSettings),
};
useEffect(() => {
changePreferencesPageTitle(intl.get('accountant'));
}, [changePreferencesPageTitle]);
const handleFormSubmit = (values, { setSubmitting }) => {
const options = transformToOptions(values);
setSubmitting(true);
const onSuccess = () => {
AppToaster.show({
message: intl.get('the_accountant_preferences_has_been_saved'),
intent: Intent.SUCCESS,
});
setSubmitting(false);
};
const onError = (errors) => {
setSubmitting(false);
};
saveSettingMutate({ options }).then(onSuccess).catch(onError);
};
return (
<Formik
initialValues={initialValues}
validationSchema={AccountantSchema}
onSubmit={handleFormSubmit}
component={AccountantForm}
/>
);
}
export default compose(
withSettings(
({
organizationSettings,
paymentReceiveSettings,
accountsSettings,
billPaymentSettings,
}) => ({
organizationSettings,
paymentReceiveSettings,
accountsSettings,
billPaymentSettings,
}),
),
withDashboardActions,
)(AccountantFormPage);

View File

@@ -0,0 +1,50 @@
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { useAccounts, useSaveSettings, useSettings } from 'hooks/query';
import PreferencesPageLoader from '../PreferencesPageLoader';
const AccountantFormContext = React.createContext();
/**
* Accountant data provider.
*/
function AccountantFormProvider({ ...props }) {
// Fetches the accounts list.
const { isLoading: isAccountsLoading, data: accounts } = useAccounts();
//Fetches Organization Settings.
const { isLoading: isSettingsLoading } = useSettings();
// Save Organization Settings.
const { mutateAsync: saveSettingMutate } = useSaveSettings();
// Provider state.
const provider = {
accounts,
isAccountsLoading,
saveSettingMutate,
};
const isLoading = isSettingsLoading || isAccountsLoading;
return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT,
)}
>
<div className={classNames(CLASSES.CARD)}>
{isLoading ? (
<PreferencesPageLoader />
) : (
<AccountantFormContext.Provider value={provider} {...props} />
)}
</div>
</div>
);
}
const useAccountantFormContext = () => React.useContext(AccountantFormContext);
export { AccountantFormProvider, useAccountantFormContext };

View File

@@ -0,0 +1,37 @@
export const transformToOptions = (option) => {
return [
{
key: 'accounting_basis',
value: option.accounting_basis,
group: 'organization',
},
{
key: 'withdrawal_account',
value: option.withdrawal_account,
group: 'bill_payments',
},
{
key: 'deposit_account',
value: option.deposit_account,
group: 'payment_receives',
},
{
key: 'advance_deposit',
value: option.advance_deposit,
group: 'payment_receives',
},
{
key: 'account_code_required',
value: option.account_code_required,
group: 'accounts',
},
{
key: 'account_code_unique',
value: option.account_code_unique,
group: 'accounts',
},
];
};

View File

@@ -0,0 +1,33 @@
import React from 'react';
import {Tabs, Tab} from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import PreferencesSubContent from 'components/Preferences/PreferencesSubContent';
import { FormattedMessage as T } from 'components';
export default function AccountsPreferences() {
const history = useHistory();
const onChangeTabs = (currentTabId) => {
switch(currentTabId) {
default:
history.push('/preferences/accounts/general');
break;
case 'custom_fields':
history.push('/preferences/accounts/custom_fields');
break;
}
};
return (
<div class="preferences__inside-content preferences__inside-content--accounts">
<Tabs
animate={true}
large={true}
onChange={onChangeTabs}>
<Tab id="general" title={<T id={'general'}/>} />
<Tab id="custom_fields" title={<T id={'custom_fields'}/>} />
</Tabs>
<PreferencesSubContent preferenceTab="accounts" />
</div>
);
}

View File

@@ -0,0 +1,83 @@
import React, { useEffect } from 'react';
import {
Popover,
Button,
Menu,
MenuDivider,
MenuItem,
Position,
Icon
} from '@blueprintjs/core';
import {
GridComponent,
ColumnsDirective,
ColumnDirective,
} from '@syncfusion/ej2-react-grids';
import useAsync from 'hooks/async';
import {connect} from 'react-redux';
import {
fetchResourceFields,
} from 'store/customFields/customFields.actions';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
function AccountsCustomFields({ fetchResourceFields, fields }) {
const fetchHook = useAsync(async () => {
await Promise.all([
// fetchResourceFields('accounts'),
]);
}, false);
useEffect(() => { fetchHook.execute(); }, []);
const actionMenuList = (column) => (
<Menu>
<MenuItem text={<T id={'view_details'}/>} />
<MenuDivider />
<MenuItem text={<T id={'edit_account'}/>} />
<MenuItem text={<T id={'new_account'}/>} />
<MenuDivider />
<MenuItem text={<T id={'inactivate_account'}/>} />
<MenuItem text={<T id={'delete_account'}/>} />
</Menu>
);
const statusRowTemplate = (column) => {
return ('Active');
};
const actionsRowTemplate = (column) => (
<Popover content={actionMenuList(column)} position={Position.RIGHT_BOTTOM}>
<Button icon={<Icon icon="ellipsis-h" />} />
</Popover>
);
const columns = [
{field: 'label_name', headerText: 'Field Label'},
{field: 'data_type', headerText: 'Type'},
{template: statusRowTemplate, headerText: 'Status'},
{template: actionsRowTemplate, headerText: ''},
];
return (
<div class="preferences__inside-content-tab preferences__inside-content-tab--custom-fields">
<GridComponent dataSource={fields}>
<ColumnsDirective>
{columns.map((column) => {
return (<ColumnDirective
field={column.field}
headerText={column.headerText}
template={column.template} />);
})}
</ColumnsDirective>
</GridComponent>
</div>
);
}
const mapStateToProps = (state) => ({
fields: state.fields.custom_fields['accounts'] || [],
});
const mapDispatchToProps = (dispatch) => ({
fetchResourceFields: (resourceSlug) => dispatch(fetchResourceFields({ resourceSlug })),
});
export default connect(mapStateToProps, mapDispatchToProps)(AccountsCustomFields);

View File

@@ -0,0 +1,9 @@
import React from 'react';
export default function AccountsGeneralPreferences() {
return (
<div class="preferences__inside-content preferences__inside-content--general">
</div>
);
}

View File

@@ -0,0 +1,20 @@
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import CurrenciesList from './CurrenciesList';
export default function PreferencesCurrenciesPage() {
return (
<div className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_CURRENCIES,
)}>
<div className={classNames(CLASSES.CARD)}>
<CurrenciesList />
</div>
</div>
)
}

View File

@@ -0,0 +1,26 @@
import React, { useCallback } from 'react';
import { Button, Intent } from '@blueprintjs/core';
import Icon from 'components/Icon';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
import { FormattedMessage as T } from 'components';
function CurrenciesActions({ openDialog }) {
const handleClickNewCurrency = useCallback(() => {
openDialog('currency-form');
}, [openDialog]);
return (
<div class="users-actions">
<Button
icon={<Icon icon="plus" iconSize={12} />}
onClick={handleClickNewCurrency}
intent={Intent.PRIMARY}
>
<T id={'new_currency'} />
</Button>
</div>
);
}
export default compose(withDialogActions)(CurrenciesActions);

View File

@@ -0,0 +1,10 @@
import React from 'react';
import CurrencyDeleteAlert from 'containers/Alerts/Currencies/CurrencyDeleteAlert';
export default function CurrenciesAlerts() {
return (
<div>
<CurrencyDeleteAlert name={'currency-delete'} />
</div>
);
}

View File

@@ -0,0 +1,70 @@
import React, { useCallback } from 'react';
import { compose } from 'utils';
import { DataTable } from 'components';
import { useCurrenciesContext } from './CurrenciesProvider';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import { ActionMenuList, useCurrenciesTableColumns } from './components';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertActions from 'containers/Alert/withAlertActions';
/**
* Currencies table.
*/
function CurrenciesDataTable({
// #ownProps
tableProps,
// #withDialog.
openDialog,
// #withAlertActions
openAlert,
}) {
const { currencies, isCurrenciesLoading } = useCurrenciesContext();
// Table columns.
const columns = useCurrenciesTableColumns();
// Handle Edit Currency.
const handleEditCurrency = useCallback(
(currency) => {
openDialog('currency-form', {
action: 'edit',
currency: currency,
});
},
[openDialog],
);
// Handle delete currency.
const handleDeleteCurrency = ({ currency_code }) => {
openAlert('currency-delete', { currency_code: currency_code });
};
return (
<DataTable
columns={columns}
data={currencies}
loading={isCurrenciesLoading}
progressBarLoading={isCurrenciesLoading}
TableLoadingRenderer={TableSkeletonRows}
ContextMenu={ActionMenuList}
noInitialFetch={true}
payload={{
onDeleteCurrency: handleDeleteCurrency,
onEditCurrency: handleEditCurrency,
}}
rowContextMenu={ActionMenuList}
{...tableProps}
/>
);
}
export default compose(
withDialogActions,
withAlertActions,
)(CurrenciesDataTable);

View File

@@ -0,0 +1,30 @@
import React, { useEffect } from 'react';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import { CurrenciesProvider } from './CurrenciesProvider';
import CurrenciesDataTable from './CurrenciesDataTable';
import CurrenciesAlerts from './CurrenciesAlerts';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose } from 'utils';
function CurrenciesList({
// #withDashboardActions
changePreferencesPageTitle,
}) {
useEffect(() => {
changePreferencesPageTitle(intl.get('currencies'));
}, [changePreferencesPageTitle]);
return (
<CurrenciesProvider>
<CurrenciesDataTable />
<CurrenciesAlerts />
</CurrenciesProvider>
);
}
export default compose(withDashboardActions)(CurrenciesList);

View File

@@ -0,0 +1,25 @@
import React, { createContext, useContext } from 'react';
import { useCurrencies } from 'hooks/query';
const CurrenciesContext = createContext();
/**
* currencies provider.
*/
function CurrenciesProvider({ ...props }) {
// fetches the currencies list.
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
const state = {
currencies,
isCurrenciesLoading,
};
return (
<CurrenciesContext.Provider value={state} {...props} />
);
}
const useCurrenciesContext = () => useContext(CurrenciesContext);
export { CurrenciesProvider, useCurrenciesContext };

View File

@@ -0,0 +1,85 @@
import React, { useMemo } from 'react';
import {
Menu,
Popover,
Button,
Position,
MenuItem,
Intent,
} from '@blueprintjs/core';
import intl from 'react-intl-universal';
import { Icon } from 'components';
import { safeCallback } from 'utils';
/**
* Row actions menu list.
*/
export function ActionMenuList({
row: { original },
payload: { onEditCurrency, onDeleteCurrency },
}) {
return (
<Menu>
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('edit_currency')}
onClick={safeCallback(onEditCurrency, original)}
/>
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('delete_currency')}
onClick={safeCallback(onDeleteCurrency, original)}
intent={Intent.DANGER}
/>
</Menu>
);
}
/**
* Actions cell.
*/
export const ActionsCell = (props) => {
return (
<Popover
position={Position.RIGHT_BOTTOM}
content={<ActionMenuList {...props} />}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
);
};
export function useCurrenciesTableColumns() {
return useMemo(
() => [
{
Header: intl.get('currency_name'),
accessor: 'currency_name',
width: 150,
},
{
Header: intl.get('currency_code'),
accessor: 'currency_code',
className: 'currency_code',
width: 120,
},
{
Header: 'Currency sign',
width: 120,
accessor: 'currency_sign'
},
{
id: 'actions',
Header: '',
Cell: ActionsCell,
className: 'actions',
width: 50,
disableResizing: true,
},
],
[],
);
}

View File

@@ -0,0 +1,8 @@
import React from 'react';
import { Redirect } from 'react-router-dom';
export default function DefaultRoute() {
const defaultTab = '/preferences/general';
return (<Redirect from='/preferences' to={defaultTab} />);
}

View File

@@ -0,0 +1,15 @@
import React from 'react';
import GeneralFormPage from './GeneralFormPage';
import { GeneralFormProvider } from './GeneralFormProvider';
/**
* Preferences - General form.
*/
export default function GeneralPreferences() {
return (
<GeneralFormProvider>
<GeneralFormPage />
</GeneralFormProvider>
);
}

View File

@@ -0,0 +1,31 @@
import * as Yup from 'yup';
import intl from 'react-intl-universal';
const Schema = Yup.object().shape({
name: Yup.string()
.required()
.label(intl.get('organization_name_')),
industry: Yup.string()
.nullable()
.label(intl.get('organization_industry_')),
location: Yup.string()
.nullable()
.label(intl.get('location')),
base_currency: Yup.string()
.required()
.label(intl.get('base_currency_')),
fiscal_year: Yup.string()
.required()
.label(intl.get('fiscal_year_')),
language: Yup.string()
.required()
.label(intl.get('language')),
timezone: Yup.string()
.required()
.label(intl.get('time_zone_')),
date_format: Yup.string()
.required()
.label(intl.get('date_format_')),
});
export const PreferencesGeneralSchema = Schema;

View File

@@ -0,0 +1,255 @@
import { Form } from 'formik';
import React from 'react';
import { Button, FormGroup, InputGroup, Intent } from '@blueprintjs/core';
import classNames from 'classnames';
import { TimezonePicker } from '@blueprintjs/timezone';
import { ErrorMessage, FastField } from 'formik';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T } from 'components';
import { ListSelect, FieldRequiredHint } from 'components';
import { inputIntent } from 'utils';
import { CLASSES } from 'common/classes';
import { getCountries } from 'common/countries';
import { getAllCurrenciesOptions } from 'common/currencies';
import { getFiscalYear } from 'common/fiscalYearOptions';
import { getLanguages } from 'common/languagesOptions';
import { useGeneralFormContext } from './GeneralFormProvider';
import { shouldBaseCurrencyUpdate } from './utils';
/**
* Preferences general form.
*/
export default function PreferencesGeneralForm({ isSubmitting }) {
const history = useHistory();
const FiscalYear = getFiscalYear();
const Countries = getCountries();
const Languages = getLanguages();
const Currencies = getAllCurrenciesOptions();
const { dateFormats, baseCurrencyMutateAbility } = useGeneralFormContext();
const baseCurrencyDisabled = baseCurrencyMutateAbility.length > 0;
// Handle close click.
const handleCloseClick = () => {
history.go(-1);
};
return (
<Form>
{/* ---------- Organization name ---------- */}
<FastField name={'name'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'organization_name'} />}
labelInfo={<FieldRequiredHint />}
inline={true}
intent={inputIntent({ error, touched })}
className={'form-group--org-name'}
helperText={<T id={'shown_on_sales_forms_and_purchase_orders'} />}
>
<InputGroup medium={'true'} {...field} />
</FormGroup>
)}
</FastField>
{/* ---------- Industry ---------- */}
<FastField name={'industry'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'organization_industry'} />}
inline={true}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="industry" />}
className={'form-group--org-industry'}
>
<InputGroup medium={'true'} {...field} />
</FormGroup>
)}
</FastField>
{/* ---------- Location ---------- */}
<FastField name={'location'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'business_location'} />}
className={classNames(
'form-group--business-location',
CLASSES.FILL,
)}
inline={true}
helperText={<ErrorMessage name="location" />}
intent={inputIntent({ error, touched })}
>
<ListSelect
items={Countries}
onItemSelect={({ value }) => {
form.setFieldValue('location', value);
}}
selectedItem={value}
selectedItemProp={'value'}
defaultText={<T id={'select_business_location'} />}
textProp={'name'}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</FastField>
{/* ---------- Base currency ---------- */}
<FastField
name={'base_currency'}
baseCurrencyDisabled={baseCurrencyDisabled}
shouldUpdate={shouldBaseCurrencyUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'base_currency'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--base-currency', CLASSES.FILL)}
inline={true}
intent={inputIntent({ error, touched })}
helperText={
<T
id={
'you_can_t_change_the_base_currency_as_there_are_transactions'
}
/>
}
>
<ListSelect
items={Currencies}
onItemSelect={(currency) => {
form.setFieldValue('base_currency', currency.key);
}}
selectedItem={value}
selectedItemProp={'key'}
defaultText={<T id={'select_base_currency'} />}
textProp={'name'}
labelProp={'key'}
popoverProps={{ minimal: true }}
disabled={baseCurrencyDisabled}
/>
</FormGroup>
)}
</FastField>
{/* --------- Fiscal Year ----------- */}
<FastField name={'fiscal_year'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'fiscal_year'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--fiscal-year', CLASSES.FILL)}
inline={true}
intent={inputIntent({ error, touched })}
helperText={<T id={'for_reporting_you_can_specify_any_month'} />}
>
<ListSelect
items={FiscalYear}
onItemSelect={(option) => {
form.setFieldValue('fiscal_year', option.key)
}}
selectedItem={value}
selectedItemProp={'key'}
defaultText={<T id={'select_fiscal_year'} />}
textProp={'name'}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</FastField>
{/* ---------- Language ---------- */}
<FastField name={'language'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'language'} />}
labelInfo={<FieldRequiredHint />}
inline={true}
className={classNames('form-group--language', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="language" />}
>
<ListSelect
items={Languages}
selectedItemProp={'value'}
textProp={'name'}
defaultText={<T id={'select_language'} />}
selectedItem={value}
onItemSelect={(item) =>
form.setFieldValue('language', item.value)
}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</FastField>
{/* ---------- Time zone ---------- */}
<FastField name={'timezone'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'time_zone'} />}
labelInfo={<FieldRequiredHint />}
inline={true}
className={classNames(
'form-group--time-zone',
CLASSES.FORM_GROUP_LIST_SELECT,
CLASSES.FILL,
)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="timezone" />}
>
<TimezonePicker
value={value}
onChange={(timezone) => {
form.setFieldValue('timezone', timezone);
}}
valueDisplayFormat="composite"
placeholder={<T id={'select_time_zone'} />}
/>
</FormGroup>
)}
</FastField>
{/* --------- Data format ----------- */}
<FastField name={'date_format'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'date_format'} />}
labelInfo={<FieldRequiredHint />}
inline={true}
className={classNames('form-group--date-format', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date_format" />}
>
<ListSelect
items={dateFormats}
onItemSelect={(dateFormat) => {
form.setFieldValue('date_format', dateFormat.key);
}}
selectedItem={value}
selectedItemProp={'key'}
defaultText={<T id={'select_date_format'} />}
textProp={'label'}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</FastField>
<div className={'card__footer'}>
<Button loading={isSubmitting} intent={Intent.PRIMARY} type="submit">
<T id={'save'} />
</Button>
<Button onClick={handleCloseClick}>
<T id={'close'} />
</Button>
</div>
</Form>
);
}

View File

@@ -0,0 +1,79 @@
import React, { useEffect } from 'react';
import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import 'style/pages/Preferences/GeneralForm.scss';
import { AppToaster } from 'components';
import GeneralForm from './GeneralForm';
import { PreferencesGeneralSchema } from './General.schema';
import { useGeneralFormContext } from './GeneralFormProvider';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose } from 'utils';
import { transformToForm } from '../../../utils';
const defaultValues = {
name: '',
industry: '',
location: '',
base_currency: '',
language: '',
fiscal_year: '',
date_format: '',
timezone: '',
};
/**
* Preferences - General form Page.
*/
function GeneralFormPage({
// #withDashboardActions
changePreferencesPageTitle,
}) {
const { updateOrganization, organization } = useGeneralFormContext();
useEffect(() => {
changePreferencesPageTitle(intl.get('general'));
}, [changePreferencesPageTitle]);
// Initial values.
const initialValues = {
...transformToForm(organization.metadata, defaultValues),
};
const handleFormSubmit = (values, { setSubmitting, resetForm }) => {
// Handle request success.
const onSuccess = (response) => {
AppToaster.show({
message: intl.get('preferences.general.success_message'),
intent: Intent.SUCCESS,
});
setSubmitting(false);
// Reboot the application if the application's language is mutated.
if (organization.metadata?.language !== values.language) {
window.location.reload();
}
};
// Handle request error.
const onError = (errors) => {
setSubmitting(false);
};
updateOrganization({ ...values })
.then(onSuccess)
.catch(onError);
};
return (
<Formik
initialValues={initialValues}
validationSchema={PreferencesGeneralSchema}
onSubmit={handleFormSubmit}
component={GeneralForm}
/>
);
}
export default compose(withDashboardActions)(GeneralFormPage);

View File

@@ -0,0 +1,61 @@
import React, { createContext } from 'react';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import {
useCurrentOrganization,
useUpdateOrganization,
useDateFormats,
useOrgBaseCurrencyMutateAbilities,
} from 'hooks/query';
import PreferencesPageLoader from '../PreferencesPageLoader';
const GeneralFormContext = createContext();
/**
* General form provider.
*/
function GeneralFormProvider({ ...props }) {
// Fetches current organization information.
const { isLoading: isOrganizationLoading, data: organization } =
useCurrentOrganization();
const { data: dateFormats, isLoading: isDateFormatsLoading } =
useDateFormats();
const { data: baseCurrencyMutateAbility } =
useOrgBaseCurrencyMutateAbilities();
// Mutate organization information.
const { mutateAsync: updateOrganization } = useUpdateOrganization();
// Provider state.
const provider = {
isOrganizationLoading,
isDateFormatsLoading,
updateOrganization,
baseCurrencyMutateAbility,
organization,
dateFormats,
};
return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_GENERAL,
)}
>
<div className={classNames(CLASSES.CARD)}>
{isOrganizationLoading || isDateFormatsLoading ? (
<PreferencesPageLoader />
) : (
<GeneralFormContext.Provider value={provider} {...props} />
)}
</div>
</div>
);
}
const useGeneralFormContext = () => React.useContext(GeneralFormContext);
export { GeneralFormProvider, useGeneralFormContext };

View File

@@ -0,0 +1,8 @@
import { defaultFastFieldShouldUpdate } from 'utils';
export const shouldBaseCurrencyUpdate = (newProps, oldProps) => {
return (
newProps.baseCurrencyDisabled !== oldProps.baseCurrencyDisabled ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -0,0 +1,9 @@
import * as Yup from 'yup';
const Schema = Yup.object().shape({
preferred_sell_account: Yup.number().nullable(),
preferred_cost_account: Yup.number().nullable(),
preferred_inventory_account: Yup.number().nullable(),
});
export const ItemPreferencesSchema = Schema;

View File

@@ -0,0 +1,148 @@
import React from 'react';
import { Form, FastField, useFormikContext } from 'formik';
import { FormGroup, Button, Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import {
AccountsSelectList,
FieldRequiredHint,
FormattedMessage as T,
} from 'components';
import { inputIntent } from 'utils';
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from 'common/accountTypes';
import { useItemPreferencesFormContext } from './ItemPreferencesFormProvider';
/**
* Item preferences form.
*/
export default function ItemForm() {
const history = useHistory();
const { accounts } = useItemPreferencesFormContext();
const { isSubmitting } = useFormikContext();
const handleCloseClick = () => {
history.go(-1);
};
return (
<Form>
{/* ----------- preferred sell account ----------- */}
<FastField name={'preferred_sell_account'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={
<strong>
<T id={'preferred_sell_account'} />
</strong>
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_after_customer_make_payment'
}
/>
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={({ id }) => {
setFieldValue('preferred_sell_account', id);
}}
selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
/>
</FormGroup>
)}
</FastField>
{/* ----------- preferred cost account ----------- */}
<FastField name={'preferred_cost_account'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={
<strong>
<T id={'preferred_cost_account'} />
</strong>
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_after_customer_make_payment'
}
/>
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={({ id }) => {
setFieldValue('preferred_cost_account', id);
}}
selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
/>
</FormGroup>
)}
</FastField>
{/* ----------- preferred inventory account ----------- */}
<FastField name={'preferred_inventory_account'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={
<strong>
<T id={'preferred_inventory_account'} />
</strong>
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_vendor_advanced_deposits'
}
/>
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={({ id }) => {
setFieldValue('preferred_inventory_account', id);
}}
selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />}
filterByTypes={[ACCOUNT_TYPE.INVENTORY]}
/>
</FormGroup>
)}
</FastField>
<div className={'card__footer'}>
<Button intent={Intent.PRIMARY} loading={isSubmitting} type="submit">
<T id={'save'} />
</Button>
<Button onClick={handleCloseClick} disabled={isSubmitting}>
<T id={'close'} />
</Button>
</div>
</Form>
);
}

View File

@@ -0,0 +1,70 @@
import React, { useEffect } from 'react';
import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
import intl from 'react-intl-universal';
import { ItemPreferencesSchema } from './ItemPreferences.schema';
import ItemPreferencesForm from './ItemPreferencesForm';
import { useItemPreferencesFormContext } from './ItemPreferencesFormProvider';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withSettings from 'containers/Settings/withSettings';
import { compose, optionsMapToArray, transformGeneralSettings } from 'utils';
import 'style/pages/Preferences/Accounting.scss';
// item form page preferences.
function ItemPreferencesFormPage({
// #withSettings
itemsSettings,
// #withDashboardActions
changePreferencesPageTitle,
}) {
const { saveSettingMutate } = useItemPreferencesFormContext();
// Initial values.
const initialValues = {
preferred_sell_account: '',
preferred_cost_account: '',
preferred_inventory_account: '',
...transformGeneralSettings(itemsSettings),
};
useEffect(() => {
changePreferencesPageTitle(intl.get('items'));
}, [changePreferencesPageTitle]);
// Handle form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const options = optionsMapToArray(values)
.map((option) => ({ ...option, group: 'items' }));
const onSuccess = () => {
AppToaster.show({
message: intl.get('the_items_preferences_has_been_saved'),
intent: Intent.SUCCESS,
});
setSubmitting(false);
};
const onError = (errors) => {
setSubmitting(false);
};
saveSettingMutate({ options }).then(onSuccess).catch(onError);
};
return (
<Formik
initialValues={initialValues}
validationSchema={ItemPreferencesSchema}
onSubmit={handleFormSubmit}
component={ItemPreferencesForm}
/>
);
}
export default compose(
withSettings(({ itemsSettings }) => ({ itemsSettings })),
withDashboardActions,
)(ItemPreferencesFormPage);

View File

@@ -0,0 +1,54 @@
import React, { useContext, createContext } from 'react';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { useSettingsItems, useAccounts, useSaveSettings } from 'hooks/query';
import PreferencesPageLoader from '../PreferencesPageLoader';
const ItemFormContext = createContext();
/**
* Item data provider.
*/
function ItemPreferencesFormProvider({ ...props }) {
// Fetches the accounts list.
const { isLoading: isAccountsLoading, data: accounts } = useAccounts();
const {
isLoading: isItemsSettingsLoading,
isFetching: isItemsSettingsFetching,
} = useSettingsItems();
// Save Organization Settings.
const { mutateAsync: saveSettingMutate } = useSaveSettings();
// Provider state.
const provider = {
accounts,
saveSettingMutate,
};
const isLoading = isAccountsLoading || isItemsSettingsLoading;
return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT,
)}
>
<div className={classNames(CLASSES.CARD)}>
{isLoading ? (
<PreferencesPageLoader />
) : (
<ItemFormContext.Provider value={provider} {...props} />
)}
</div>
</div>
);
}
const useItemPreferencesFormContext = () => useContext(ItemFormContext);
export { useItemPreferencesFormContext, ItemPreferencesFormProvider };

View File

@@ -0,0 +1,14 @@
import React from 'react';
import ItemPreferencesFormPage from './ItemPreferencesFormPage';
import { ItemPreferencesFormProvider } from './ItemPreferencesFormProvider';
/**
* items preferences.
*/
export default function ItemsPreferences() {
return (
<ItemPreferencesFormProvider>
<ItemPreferencesFormPage />
</ItemPreferencesFormProvider>
);
}

View File

@@ -0,0 +1,23 @@
import React from 'react';
import ContentLoader from 'react-content-loader';
export default function PreferencesPageLoader(props) {
return (
<ContentLoader
speed={2}
width={400}
height={250}
viewBox="0 0 400 250"
backgroundColor="#f3f3f3"
foregroundColor="#e6e6e6"
{...props}
>
<rect x="0" y="82" rx="2" ry="2" width="200" height="20" />
<rect x="0" y="112" rx="2" ry="2" width="385" height="30" />
<rect x="0" y="0" rx="2" ry="2" width="200" height="20" />
<rect x="-1" y="30" rx="2" ry="2" width="385" height="30" />
<rect x="0" y="164" rx="2" ry="2" width="200" height="20" />
<rect x="0" y="194" rx="2" ry="2" width="385" height="30" />
</ContentLoader>
);
}

View File

@@ -0,0 +1,39 @@
import React from 'react';
import { Tabs, Tab } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import 'style/pages/Preferences/Users.scss';
import { CLASSES } from 'common/classes';
import PreferencesSubContent from 'components/Preferences/PreferencesSubContent';
import withUserPreferences from 'containers/Preferences/Users/withUserPreferences';
/**
* Preferences page - Users page.
*/
function UsersPreferences({ openDialog }) {
const onChangeTabs = (currentTabId) => {};
return (
<div
className={classNames(
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT,
CLASSES.PREFERENCES_PAGE_INSIDE_CONTENT_USERS,
)}
>
<div className={classNames(CLASSES.CARD)}>
<div className={classNames(CLASSES.PREFERENCES_PAGE_TABS)}>
<Tabs animate={true} onChange={onChangeTabs}>
<Tab id="users" title={intl.get('users')} />
<Tab id="roles" title={intl.get('roles')} />
</Tabs>
</div>
<PreferencesSubContent preferenceTab="users" />
</div>
</div>
);
}
export default withUserPreferences(UsersPreferences);

View File

@@ -0,0 +1,40 @@
import React from 'react';
import {
Button,
Intent,
} from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import Icon from 'components/Icon';
import withDialogActions from 'containers/Dialog/withDialogActions';
import {compose} from 'utils';
function UsersActions({
openDialog,
closeDialog,
}) {
const onClickNewUser = () => {
openDialog('invite-user');
};
return (
<div className="preferences-actions">
<Button
icon={<Icon icon='plus' iconSize={12} />}
onClick={onClickNewUser}
intent={Intent.PRIMARY}>
<T id={'invite_user'} />
</Button>
<Button
icon={<Icon icon='plus' iconSize={12} />}
onClick={onClickNewUser}>
<T id={'new_role'} />
</Button>
</div>
);
}
export default compose(
withDialogActions,
)(UsersActions);

View File

@@ -0,0 +1,14 @@
import React from 'react';
import UserDeleteAlert from 'containers/Alerts/Users/UserDeleteAlert';
import UserInactivateAlert from 'containers/Alerts/Users/UserInactivateAlert';
import UserActivateAlert from 'containers/Alerts/Users/UserActivateAlert';
export default function UsersAlerts() {
return (
<>
<UserDeleteAlert name={'user-delete'} />
<UserInactivateAlert name={'user-inactivate'} />
<UserActivateAlert name={'user-activate'} />
</>
);
}

View File

@@ -0,0 +1,105 @@
import React, { useCallback } from 'react';
import { compose } from 'utils';
import { DataTable } from 'components';
import { useResendInvitation } from 'hooks/query';
import AppToaster from 'components/AppToaster';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertActions from 'containers/Alert/withAlertActions';
import { ActionsMenu, useUsersListColumns } from './components';
import { useUsersListContext } from './UsersProvider';
import { Intent } from '@blueprintjs/core';
/**
* Users datatable.
*/
function UsersDataTable({
// #withDialogActions
openDialog,
// #withAlertActions
openAlert,
}) {
// Handle edit user action.
const handleEditUserAction = useCallback(
(user) => {
openDialog('user-form', { action: 'edit', userId: user.id });
},
[openDialog],
);
// Handle inactivate user action.
const handleInactivateUser = useCallback(
(user) => {
openAlert('user-inactivate', { userId: user.id });
},
[openAlert]
);
// Handle activate user action.
const handleActivateuser = useCallback(
(user) => {
openAlert('user-activate', { userId: user.id });
},
[openAlert]
);
// Handle delete user action.
const handleDeleteUser = useCallback(
(user) => {
openAlert('user-delete', { userId: user.id });
},
[openAlert]
);
const { mutateAsync: resendInviation } = useResendInvitation();
const handleResendInvitation = useCallback(
(user) => {
resendInviation(user.id).then(() => {
AppToaster.show({
message: 'User invitation has been re-sent to the user.',
intent: Intent.SUCCESS
});
}).catch(({ response: { data: { errors } } }) => {
if (errors.find(e => e.type === 'USER_RECENTLY_INVITED')) {
AppToaster.show({
message: 'This person was recently invited. No need to invite them again just yet.',
intent: Intent.DANGER
});
}
});
}
)
// Users list columns.
const columns = useUsersListColumns();
// Users list context.
const { users, isUsersLoading, isUsersFetching } = useUsersListContext();
return (
<DataTable
columns={columns}
data={users}
loading={isUsersLoading}
headerLoading={isUsersLoading}
progressBarLoading={isUsersFetching}
TableLoadingRenderer={TableSkeletonRows}
noInitialFetch={true}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditUserAction,
onActivate: handleActivateuser,
onInactivate: handleInactivateUser,
onDelete: handleDeleteUser,
onResendInvitation: handleResendInvitation
}}
/>
);
}
export default compose(
withDialogActions,
withAlertActions
)(UsersDataTable);

View File

@@ -0,0 +1,33 @@
import React, { useEffect } from 'react';
import intl from 'react-intl-universal';
import {UsersListProvider } from './UsersProvider';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import UsersDataTable from './UsersDataTable';
import UsersAlerts from './UsersAlerts';
import { compose } from 'utils';
/**
* Users list.
*/
function UsersListPreferences({
// #withDashboardActions
changePreferencesPageTitle,
}) {
useEffect(() => {
changePreferencesPageTitle(intl.get('users'));
}, [changePreferencesPageTitle]);
return (
<UsersListProvider>
<UsersDataTable />
<UsersAlerts />
</UsersListProvider>
);
}
export default compose(
withDashboardActions,
)(UsersListPreferences);

View File

@@ -0,0 +1,25 @@
import React, { createContext } from 'react';
import { useUsers } from 'hooks/query';
const UsersListContext = createContext();
/**
* Users list provider.
*/
function UsersListProvider(props) {
const { data: users, isLoading, isFetching } = useUsers();
const state = {
isUsersLoading: isLoading,
isUsersFetching: isFetching,
users,
};
return (
<UsersListContext.Provider value={state} {...props} />
);
}
const useUsersListContext = () => React.useContext(UsersListContext);
export { UsersListProvider, useUsersListContext };

View File

@@ -0,0 +1,160 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import {
Intent,
Button,
Popover,
Menu,
MenuDivider,
Tag,
MenuItem,
Position,
} from '@blueprintjs/core';
import { safeCallback, firstLettersArgs } from 'utils';
import { Icon, If } from 'components';
/**
* Avatar cell.
*/
function AvatarCell(row) {
return <span className={'avatar'}>{firstLettersArgs(row.email)}</span>;
}
/**
* Users table actions menu.
*/
export function ActionsMenu({
row: { original },
payload: { onEdit, onInactivate, onActivate, onDelete, onResendInvitation },
}) {
return (
<Menu>
<If condition={original.invite_accepted_at}>
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('edit_user')}
onClick={safeCallback(onEdit, original)}
/>
<MenuDivider />
{original.active ? (
<MenuItem
text={intl.get('inactivate_user')}
onClick={safeCallback(onInactivate, original)}
icon={<Icon icon="pause-16" iconSize={16} />}
/>
) : (
<MenuItem
text={intl.get('activate_user')}
onClick={safeCallback(onActivate, original)}
icon={<Icon icon="play-16" iconSize={16} />}
/>
)}
</If>
<If condition={!original.invite_accepted_at}>
<MenuItem
text={'Resend invitation'}
onClick={safeCallback(onResendInvitation, original)}
icon={<Icon icon="send" iconSize={16} />}
/>
</If>
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('delete_user')}
onClick={safeCallback(onDelete, original)}
intent={Intent.DANGER}
/>
</Menu>
);
}
/**
* Status accessor.
*/
function StatusAccessor(user) {
return !user.is_invite_accepted ? (
<Tag minimal={true}>
<T id={'inviting'} />
</Tag>
) : user.active ? (
<Tag intent={Intent.SUCCESS} minimal={true}>
<T id={'activate'} />
</Tag>
) : (
<Tag intent={Intent.WARNING} minimal={true}>
<T id={'inactivate'} />
</Tag>
);
}
/**
* Actions cell.
*/
function ActionsCell(props) {
return (
<Popover
content={<ActionsMenu {...props} />}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
);
}
function FullNameAccessor(user) {
return user.is_invite_accepted ? user.full_name : user.email;
}
export const useUsersListColumns = () => {
return React.useMemo(
() => [
{
id: 'avatar',
Header: '',
accessor: AvatarCell,
width: 40,
},
{
id: 'full_name',
Header: intl.get('full_name'),
accessor: FullNameAccessor,
width: 150,
},
{
id: 'email',
Header: intl.get('email'),
accessor: 'email',
width: 150,
},
{
id: 'phone_number',
Header: intl.get('phone_number'),
accessor: 'phone_number',
width: 120,
},
{
id: 'status',
Header: 'Status',
accessor: StatusAccessor,
width: 80,
className: 'status',
},
{
id: 'actions',
Header: '',
Cell: ActionsCell,
className: 'actions',
width: 50,
disableResizing: true,
},
],
[],
);
};

View File

@@ -0,0 +1,13 @@
import {connect} from 'react-redux';
import t from 'store/types';
export const mapStateToProps = (state, props) => {
};
export const mapDispatchToProps = (dispatch) => ({
openDialog: (name, payload) => dispatch({ type: t.OPEN_DIALOG, name, payload }),
closeDialog: (name, payload) => dispatch({ type: t.CLOSE_DIALOG, name, payload }),
});
export default connect(null, mapDispatchToProps);