mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-24 16:49:48 +00:00
Compare commits
22 Commits
BIG-427-fi
...
BIG-428-cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05c2232b97 | ||
|
|
40bddfdfeb | ||
|
|
d6e2f01d70 | ||
|
|
2344d3d34d | ||
|
|
883c5dcb41 | ||
|
|
be10b8934d | ||
|
|
ce38c71fa7 | ||
|
|
1162fbc7c3 | ||
|
|
18b9e25f2b | ||
|
|
dd26bdc482 | ||
|
|
ad3c9ebfe9 | ||
|
|
36611652da | ||
|
|
06c7ee71b4 | ||
|
|
54d3188666 | ||
|
|
1249415054 | ||
|
|
6c96c371c5 | ||
|
|
6c61a69f10 | ||
|
|
981b65349d | ||
|
|
a7d29a31c8 | ||
|
|
c1d92b74f0 | ||
|
|
6f0f47f38a | ||
|
|
83510cfa70 |
@@ -32,3 +32,8 @@ CONTACT_US_MAIL=support@bigcapital.ly
|
|||||||
# Agendash
|
# Agendash
|
||||||
AGENDASH_AUTH_USER=agendash
|
AGENDASH_AUTH_USER=agendash
|
||||||
AGENDASH_AUTH_PASSWORD=123123
|
AGENDASH_AUTH_PASSWORD=123123
|
||||||
|
|
||||||
|
# Sign-up restrictions
|
||||||
|
SIGNUP_DISABLED=true
|
||||||
|
SIGNUP_ALLOWED_DOMAINS=
|
||||||
|
SIGNUP_ALLOWED_EMAILS=
|
||||||
@@ -72,6 +72,11 @@ services:
|
|||||||
- AGENDASH_AUTH_USER=${AGENDASH_AUTH_USER}
|
- AGENDASH_AUTH_USER=${AGENDASH_AUTH_USER}
|
||||||
- AGENDASH_AUTH_PASSWORD=${AGENDASH_AUTH_PASSWORD}
|
- AGENDASH_AUTH_PASSWORD=${AGENDASH_AUTH_PASSWORD}
|
||||||
|
|
||||||
|
# Sign-up restrictions
|
||||||
|
- SIGNUP_DISABLED=${SIGNUP_DISABLED}
|
||||||
|
- SIGNUP_ALLOWED_DOMAINS=${SIGNUP_ALLOWED_DOMAINS}
|
||||||
|
- SIGNUP_ALLOWED_EMAILS=${SIGNUP_ALLOWED_EMAILS}
|
||||||
|
|
||||||
database_migration:
|
database_migration:
|
||||||
container_name: bigcapital-database-migration
|
container_name: bigcapital-database-migration
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -34,7 +34,11 @@ ARG MAIL_HOST= \
|
|||||||
BASE_URL= \
|
BASE_URL= \
|
||||||
# Agendash
|
# Agendash
|
||||||
AGENDASH_AUTH_USER=agendash \
|
AGENDASH_AUTH_USER=agendash \
|
||||||
AGENDASH_AUTH_PASSWORD=123123
|
AGENDASH_AUTH_PASSWORD=123123 \
|
||||||
|
# Sign-up restriction
|
||||||
|
SIGNUP_DISABLED= \
|
||||||
|
SIGNUP_ALLOWED_DOMAINS= \
|
||||||
|
SIGNUP_ALLOWED_EMAILS=
|
||||||
|
|
||||||
ENV MAIL_HOST=$MAIL_HOST \
|
ENV MAIL_HOST=$MAIL_HOST \
|
||||||
MAIL_USERNAME=$MAIL_USERNAME \
|
MAIL_USERNAME=$MAIL_USERNAME \
|
||||||
@@ -68,7 +72,11 @@ ENV MAIL_HOST=$MAIL_HOST \
|
|||||||
# MongoDB
|
# MongoDB
|
||||||
MONGODB_DATABASE_URL=$MONGODB_DATABASE_URL \
|
MONGODB_DATABASE_URL=$MONGODB_DATABASE_URL \
|
||||||
# Application
|
# Application
|
||||||
BASE_URL=$BASE_URL
|
BASE_URL=$BASE_URL \
|
||||||
|
# Sign-up restriction
|
||||||
|
SIGNUP_DISABLED=$SIGNUP_DISABLED \
|
||||||
|
SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \
|
||||||
|
SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS
|
||||||
|
|
||||||
# Create app directory.
|
# Create app directory.
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import { check, param, query } from 'express-validator';
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
import BaseController from '@/api/controllers/BaseController';
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
import { AbilitySubject, AccountAction, IAccountDTO } from '@/interfaces';
|
import {
|
||||||
|
AbilitySubject,
|
||||||
|
AccountAction,
|
||||||
|
IAccountDTO,
|
||||||
|
IAccountsStructureType,
|
||||||
|
} from '@/interfaces';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
import { DATATYPES_LENGTH } from '@/data/DataTypes';
|
import { DATATYPES_LENGTH } from '@/data/DataTypes';
|
||||||
@@ -172,6 +177,11 @@ export default class AccountsController extends BaseController {
|
|||||||
|
|
||||||
query('inactive_mode').optional().isBoolean().toBoolean(),
|
query('inactive_mode').optional().isBoolean().toBoolean(),
|
||||||
query('search_keyword').optional({ nullable: true }).isString().trim(),
|
query('search_keyword').optional({ nullable: true }).isString().trim(),
|
||||||
|
|
||||||
|
query('structure')
|
||||||
|
.optional()
|
||||||
|
.isString()
|
||||||
|
.isIn([IAccountsStructureType.Tree, IAccountsStructureType.Flat]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,6 +351,7 @@ export default class AccountsController extends BaseController {
|
|||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
inactiveMode: false,
|
inactiveMode: false,
|
||||||
|
structure: IAccountsStructureType.Tree,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export default class AuthenticationController extends BaseController {
|
|||||||
asyncMiddleware(this.resetPassword.bind(this)),
|
asyncMiddleware(this.resetPassword.bind(this)),
|
||||||
this.handlerErrors
|
this.handlerErrors
|
||||||
);
|
);
|
||||||
|
router.get('/meta', asyncMiddleware(this.getAuthMeta.bind(this)));
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +208,23 @@ export default class AuthenticationController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the authentication meta for SPA.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {Function} next
|
||||||
|
* @returns {Response|void}
|
||||||
|
*/
|
||||||
|
private async getAuthMeta(req: Request, res: Response, next: Function) {
|
||||||
|
try {
|
||||||
|
const meta = await this.authApplication.getAuthMeta();
|
||||||
|
|
||||||
|
return res.status(200).send({ meta });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the service errors.
|
* Handles the service errors.
|
||||||
*/
|
*/
|
||||||
@@ -247,6 +265,30 @@ export default class AuthenticationController extends BaseController {
|
|||||||
errors: [{ type: 'EMAIL.EXISTS', code: 600 }],
|
errors: [{ type: 'EMAIL.EXISTS', code: 600 }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (error.errorType === 'SIGNUP_RESTRICTED') {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
type: 'SIGNUP_RESTRICTED',
|
||||||
|
message:
|
||||||
|
'Sign-up is restricted no one can sign-up to the system.',
|
||||||
|
code: 700,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'SIGNUP_RESTRICTED_NOT_ALLOWED') {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
type: 'SIGNUP_RESTRICTED_NOT_ALLOWED',
|
||||||
|
message:
|
||||||
|
'Sign-up is restricted the given email address is not allowed to sign-up.',
|
||||||
|
code: 710,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default class BalanceSheetStatementController extends BaseFinancialReport
|
|||||||
get balanceSheetValidationSchema(): ValidationChain[] {
|
get balanceSheetValidationSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
...this.sheetNumberFormatValidationSchema,
|
...this.sheetNumberFormatValidationSchema,
|
||||||
query('accounting_method').optional().isIn(['cash', 'accural']),
|
query('accounting_method').optional().isIn(['cash', 'accrual']),
|
||||||
|
|
||||||
query('from_date').optional(),
|
query('from_date').optional(),
|
||||||
query('to_date').optional(),
|
query('to_date').optional(),
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
|
|||||||
try {
|
try {
|
||||||
const { data, query, meta } =
|
const { data, query, meta } =
|
||||||
await this.generalLedgetService.generalLedger(tenantId, filter);
|
await this.generalLedgetService.generalLedger(tenantId, filter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
meta: this.transfromToResponse(meta),
|
meta: this.transfromToResponse(meta),
|
||||||
data: this.transfromToResponse(data),
|
data: this.transfromToResponse(data),
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default class OrganizationController extends BaseController {
|
|||||||
private get organizationValidationSchema(): ValidationChain[] {
|
private get organizationValidationSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
check('name').exists().trim(),
|
check('name').exists().trim(),
|
||||||
check('industry').optional().isString(),
|
check('industry').optional({ nullable: true }).isString().trim().escape(),
|
||||||
check('location').exists().isString().isISO31661Alpha2(),
|
check('location').exists().isString().isISO31661Alpha2(),
|
||||||
check('base_currency').exists().isISO4217(),
|
check('base_currency').exists().isISO4217(),
|
||||||
check('timezone').exists().isIn(moment.tz.names()),
|
check('timezone').exists().isIn(moment.tz.names()),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { castCommaListEnvVarToArray, parseBoolean } from '@/utils';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
@@ -146,6 +147,19 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign-up restrictions
|
||||||
|
*/
|
||||||
|
signupRestrictions: {
|
||||||
|
disabled: parseBoolean<boolean>(process.env.SIGNUP_DISABLED, false),
|
||||||
|
allowedDomains: castCommaListEnvVarToArray(
|
||||||
|
process.env.SIGNUP_ALLOWED_DOMAINS
|
||||||
|
),
|
||||||
|
allowedEmails: castCommaListEnvVarToArray(
|
||||||
|
process.env.SIGNUP_ALLOWED_EMAILS
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puppeteer remote browserless connection.
|
* Puppeteer remote browserless connection.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default class SeedSettings extends TenantSeeder {
|
|||||||
up() {
|
up() {
|
||||||
const settings = [
|
const settings = [
|
||||||
// Orgnization settings.
|
// Orgnization settings.
|
||||||
{ group: 'organization', key: 'accounting_basis', value: 'accural' },
|
{ group: 'organization', key: 'accounting_basis', value: 'accrual' },
|
||||||
|
|
||||||
// Accounts settings.
|
// Accounts settings.
|
||||||
{ group: 'accounts', key: 'account_code_unique', value: true },
|
{ group: 'accounts', key: 'account_code_unique', value: true },
|
||||||
|
|||||||
@@ -79,9 +79,15 @@ export interface IAccountTransaction {
|
|||||||
}
|
}
|
||||||
export interface IAccountResponse extends IAccount {}
|
export interface IAccountResponse extends IAccount {}
|
||||||
|
|
||||||
|
export enum IAccountsStructureType {
|
||||||
|
Tree = 'tree',
|
||||||
|
Flat = 'flat',
|
||||||
|
}
|
||||||
|
|
||||||
export interface IAccountsFilter extends IDynamicListFilterDTO {
|
export interface IAccountsFilter extends IDynamicListFilterDTO {
|
||||||
stringifiedFilterRoles?: string;
|
stringifiedFilterRoles?: string;
|
||||||
onlyInactive: boolean;
|
onlyInactive: boolean;
|
||||||
|
structure?: IAccountsStructureType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAccountType {
|
export interface IAccountType {
|
||||||
|
|||||||
@@ -75,3 +75,7 @@ export interface IAuthSendedResetPassword {
|
|||||||
user: ISystemUser,
|
user: ISystemUser,
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IAuthGetMetaPOJO {
|
||||||
|
signupDisabled: boolean;
|
||||||
|
}
|
||||||
@@ -44,7 +44,7 @@ export interface IBalanceSheetQuery extends IFinancialSheetBranchesQuery {
|
|||||||
numberFormat: INumberFormatQuery;
|
numberFormat: INumberFormatQuery;
|
||||||
noneTransactions: boolean;
|
noneTransactions: boolean;
|
||||||
noneZero: boolean;
|
noneZero: boolean;
|
||||||
basis: 'cash' | 'accural';
|
basis: 'cash' | 'accrual';
|
||||||
accountIds: number[];
|
accountIds: number[];
|
||||||
|
|
||||||
percentageOfColumn: boolean;
|
percentageOfColumn: boolean;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export interface ITrialBalanceSheetQuery {
|
|||||||
fromDate: Date | string;
|
fromDate: Date | string;
|
||||||
toDate: Date | string;
|
toDate: Date | string;
|
||||||
numberFormat: INumberFormatQuery;
|
numberFormat: INumberFormatQuery;
|
||||||
basis: 'cash' | 'accural';
|
basis: 'cash' | 'accrual';
|
||||||
noneZero: boolean;
|
noneZero: boolean;
|
||||||
noneTransactions: boolean;
|
noneTransactions: boolean;
|
||||||
onlyActive: boolean;
|
onlyActive: boolean;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import moment from 'moment';
|
|||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
||||||
import { formatNumber } from 'utils';
|
import { formatNumber } from 'utils';
|
||||||
|
import { isArrayLikeObject } from 'lodash/fp';
|
||||||
|
|
||||||
export class Transformer {
|
export class Transformer {
|
||||||
public context: any;
|
public context: any;
|
||||||
@@ -39,12 +40,33 @@ export class Transformer {
|
|||||||
return object;
|
return object;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param object
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
protected preCollectionTransform = (object: any) => {
|
||||||
|
return object;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param object
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
protected postCollectionTransform = (object: any) => {
|
||||||
|
return object;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public work = (object: any) => {
|
public work = (object: any) => {
|
||||||
if (Array.isArray(object)) {
|
if (Array.isArray(object)) {
|
||||||
return object.map(this.getTransformation);
|
const preTransformed = this.preCollectionTransform(object);
|
||||||
|
const transformed = preTransformed.map(this.getTransformation);
|
||||||
|
|
||||||
|
return this.postCollectionTransform(transformed);
|
||||||
} else if (isObject(object)) {
|
} else if (isObject(object)) {
|
||||||
return this.getTransformation(object);
|
return this.getTransformation(object);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { IAccount } from '@/interfaces';
|
import { IAccount, IAccountsStructureType } from '@/interfaces';
|
||||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||||
import { formatNumber } from 'utils';
|
import {
|
||||||
|
assocDepthLevelToObjectTree,
|
||||||
|
flatToNestedArray,
|
||||||
|
formatNumber,
|
||||||
|
nestedArrayToFlatten,
|
||||||
|
} from 'utils';
|
||||||
|
|
||||||
export class AccountTransformer extends Transformer {
|
export class AccountTransformer extends Transformer {
|
||||||
/**
|
/**
|
||||||
@@ -8,7 +13,23 @@ export class AccountTransformer extends Transformer {
|
|||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
public includeAttributes = (): string[] => {
|
public includeAttributes = (): string[] => {
|
||||||
return ['formattedAmount'];
|
return ['formattedAmount', 'flattenName'];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the flatten name with all dependants accounts names.
|
||||||
|
* @param {IAccount} account -
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public flattenName = (account: IAccount): string => {
|
||||||
|
const parentDependantsIds = this.options.accountsGraph.dependantsOf(
|
||||||
|
account.id
|
||||||
|
);
|
||||||
|
const prefixAccounts = parentDependantsIds.map((dependId) => {
|
||||||
|
const node = this.options.accountsGraph.getNodeData(dependId);
|
||||||
|
return `${node.name}: `;
|
||||||
|
});
|
||||||
|
return `${prefixAccounts}${account.name}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,8 +38,28 @@ export class AccountTransformer extends Transformer {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected formattedAmount = (account: IAccount): string => {
|
protected formattedAmount = (account: IAccount): string => {
|
||||||
return formatNumber(account.amount, {
|
return formatNumber(account.amount, { currencyCode: account.currencyCode });
|
||||||
currencyCode: account.currencyCode,
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the accounts collection to flat or nested array.
|
||||||
|
* @param {IAccount[]}
|
||||||
|
* @returns {IAccount[]}
|
||||||
|
*/
|
||||||
|
protected postCollectionTransform = (accounts: IAccount[]) => {
|
||||||
|
// Transfom the flatten to accounts tree.
|
||||||
|
const transformed = flatToNestedArray(accounts, {
|
||||||
|
id: 'id',
|
||||||
|
parentId: 'parentAccountId',
|
||||||
});
|
});
|
||||||
|
// Associate `accountLevel` attr to indicate object depth.
|
||||||
|
const transformed2 = assocDepthLevelToObjectTree(
|
||||||
|
transformed,
|
||||||
|
1,
|
||||||
|
'accountLevel'
|
||||||
|
);
|
||||||
|
return this.options.structure === IAccountsStructureType.Flat
|
||||||
|
? nestedArrayToFlatten(transformed2)
|
||||||
|
: transformed2;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,15 +22,19 @@ export class GetAccount {
|
|||||||
*/
|
*/
|
||||||
public getAccount = async (tenantId: number, accountId: number) => {
|
public getAccount = async (tenantId: number, accountId: number) => {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
// Find the given account or throw not found error.
|
// Find the given account or throw not found error.
|
||||||
const account = await Account.query().findById(accountId).throwIfNotFound();
|
const account = await Account.query().findById(accountId).throwIfNotFound();
|
||||||
|
|
||||||
|
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||||
|
|
||||||
// Transformes the account model to POJO.
|
// Transformes the account model to POJO.
|
||||||
const transformed = await this.transformer.transform(
|
const transformed = await this.transformer.transform(
|
||||||
tenantId,
|
tenantId,
|
||||||
account,
|
account,
|
||||||
new AccountTransformer()
|
new AccountTransformer(),
|
||||||
|
{ accountsGraph }
|
||||||
);
|
);
|
||||||
return this.i18nService.i18nApply(
|
return this.i18nService.i18nApply(
|
||||||
[['accountTypeLabel'], ['accountNormalFormatted']],
|
[['accountTypeLabel'], ['accountNormalFormatted']],
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { IAccountsFilter, IAccountResponse, IFilterMeta } from '@/interfaces';
|
import {
|
||||||
|
IAccountsFilter,
|
||||||
|
IAccountResponse,
|
||||||
|
IFilterMeta,
|
||||||
|
IAccountsStructureType,
|
||||||
|
} from '@/interfaces';
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
import { AccountTransformer } from './AccountTransform';
|
import { AccountTransformer } from './AccountTransform';
|
||||||
@@ -38,6 +43,7 @@ export class GetAccounts {
|
|||||||
filterDTO: IAccountsFilter
|
filterDTO: IAccountsFilter
|
||||||
): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> => {
|
): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> => {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
|
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
|
|
||||||
// Parses the stringified filter roles.
|
// Parses the stringified filter roles.
|
||||||
const filter = this.parseListFilterDTO(filterDTO);
|
const filter = this.parseListFilterDTO(filterDTO);
|
||||||
@@ -53,17 +59,16 @@ export class GetAccounts {
|
|||||||
dynamicList.buildQuery()(builder);
|
dynamicList.buildQuery()(builder);
|
||||||
builder.modify('inactiveMode', filter.inactiveMode);
|
builder.modify('inactiveMode', filter.inactiveMode);
|
||||||
});
|
});
|
||||||
// Retrievs the formatted accounts collection.
|
|
||||||
const preTransformedAccounts = await this.transformer.transform(
|
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||||
|
|
||||||
|
// Retrieves the transformed accounts collection.
|
||||||
|
const transformedAccounts = await this.transformer.transform(
|
||||||
tenantId,
|
tenantId,
|
||||||
accounts,
|
accounts,
|
||||||
new AccountTransformer()
|
new AccountTransformer(),
|
||||||
|
{ accountsGraph, structure: filterDTO.structure }
|
||||||
);
|
);
|
||||||
// Transform accounts to nested array.
|
|
||||||
const transformedAccounts = flatToNestedArray(preTransformedAccounts, {
|
|
||||||
id: 'id',
|
|
||||||
parentId: 'parentAccountId',
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts: transformedAccounts,
|
accounts: transformedAccounts,
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import { Service, Inject, Container } from 'typedi';
|
import { Service, Inject, Container } from 'typedi';
|
||||||
import { IRegisterDTO, ISystemUser, IPasswordReset } from '@/interfaces';
|
import {
|
||||||
|
IRegisterDTO,
|
||||||
|
ISystemUser,
|
||||||
|
IPasswordReset,
|
||||||
|
IAuthGetMetaPOJO,
|
||||||
|
} from '@/interfaces';
|
||||||
import { AuthSigninService } from './AuthSignin';
|
import { AuthSigninService } from './AuthSignin';
|
||||||
import { AuthSignupService } from './AuthSignup';
|
import { AuthSignupService } from './AuthSignup';
|
||||||
import { AuthSendResetPassword } from './AuthSendResetPassword';
|
import { AuthSendResetPassword } from './AuthSendResetPassword';
|
||||||
|
import { GetAuthMeta } from './GetAuthMeta';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class AuthenticationApplication {
|
export default class AuthenticationApplication {
|
||||||
@@ -15,6 +21,9 @@ export default class AuthenticationApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private authResetPasswordService: AuthSendResetPassword;
|
private authResetPasswordService: AuthSendResetPassword;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private authGetMeta: GetAuthMeta;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signin and generates JWT token.
|
* Signin and generates JWT token.
|
||||||
* @throws {ServiceError}
|
* @throws {ServiceError}
|
||||||
@@ -53,4 +62,12 @@ export default class AuthenticationApplication {
|
|||||||
public async resetPassword(token: string, password: string): Promise<void> {
|
public async resetPassword(token: string, password: string): Promise<void> {
|
||||||
return this.authResetPasswordService.resetPassword(token, password);
|
return this.authResetPasswordService.resetPassword(token, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the authentication meta for SPA.
|
||||||
|
* @returns {Promise<IAuthGetMetaPOJO>}
|
||||||
|
*/
|
||||||
|
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
|
||||||
|
return this.authGetMeta.getAuthMeta();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { omit } from 'lodash';
|
import { isEmpty, omit } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import {
|
import {
|
||||||
@@ -13,6 +13,7 @@ import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|||||||
import TenantsManagerService from '../Tenancy/TenantsManager';
|
import TenantsManagerService from '../Tenancy/TenantsManager';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import { hashPassword } from '@/utils';
|
import { hashPassword } from '@/utils';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
export class AuthSignupService {
|
export class AuthSignupService {
|
||||||
@Inject()
|
@Inject()
|
||||||
@@ -33,6 +34,9 @@ export class AuthSignupService {
|
|||||||
public async signUp(signupDTO: IRegisterDTO): Promise<ISystemUser> {
|
public async signUp(signupDTO: IRegisterDTO): Promise<ISystemUser> {
|
||||||
const { systemUserRepository } = this.sysRepositories;
|
const { systemUserRepository } = this.sysRepositories;
|
||||||
|
|
||||||
|
// Validates the signup disable restrictions.
|
||||||
|
await this.validateSignupRestrictions(signupDTO.email);
|
||||||
|
|
||||||
// Validates the given email uniqiness.
|
// Validates the given email uniqiness.
|
||||||
await this.validateEmailUniqiness(signupDTO.email);
|
await this.validateEmailUniqiness(signupDTO.email);
|
||||||
|
|
||||||
@@ -74,4 +78,34 @@ export class AuthSignupService {
|
|||||||
throw new ServiceError(ERRORS.EMAIL_EXISTS);
|
throw new ServiceError(ERRORS.EMAIL_EXISTS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate sign-up disable restrictions.
|
||||||
|
* @param {string} email
|
||||||
|
*/
|
||||||
|
private async validateSignupRestrictions(email: string) {
|
||||||
|
// Can't continue if the signup is not disabled.
|
||||||
|
if (!config.signupRestrictions.disabled) return;
|
||||||
|
|
||||||
|
// Validate the allowed email addresses and domains.
|
||||||
|
if (
|
||||||
|
!isEmpty(config.signupRestrictions.allowedEmails) ||
|
||||||
|
!isEmpty(config.signupRestrictions.allowedDomains)
|
||||||
|
) {
|
||||||
|
const emailDomain = email.split('@').pop();
|
||||||
|
const isAllowedEmail =
|
||||||
|
config.signupRestrictions.allowedEmails.indexOf(email) !== -1;
|
||||||
|
|
||||||
|
const isAllowedDomain = config.signupRestrictions.allowedDomains.some(
|
||||||
|
(domain) => emailDomain === domain
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isAllowedEmail && !isAllowedDomain) {
|
||||||
|
throw new ServiceError(ERRORS.SIGNUP_RESTRICTED_NOT_ALLOWED);
|
||||||
|
}
|
||||||
|
// Throw error if the signup is disabled with no exceptions.
|
||||||
|
} else {
|
||||||
|
throw new ServiceError(ERRORS.SIGNUP_RESTRICTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
packages/server/src/services/Authentication/GetAuthMeta.ts
Normal file
16
packages/server/src/services/Authentication/GetAuthMeta.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Service } from 'typedi';
|
||||||
|
import { IAuthGetMetaPOJO } from '@/interfaces';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetAuthMeta {
|
||||||
|
/**
|
||||||
|
* Retrieves the authentication meta for SPA.
|
||||||
|
* @returns {Promise<IAuthGetMetaPOJO>}
|
||||||
|
*/
|
||||||
|
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
|
||||||
|
return {
|
||||||
|
signupDisabled: config.signupRestrictions.disabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,4 +7,6 @@ export const ERRORS = {
|
|||||||
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
|
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
|
||||||
PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_EXISTS',
|
PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_EXISTS',
|
||||||
EMAIL_EXISTS: 'EMAIL_EXISTS',
|
EMAIL_EXISTS: 'EMAIL_EXISTS',
|
||||||
|
SIGNUP_RESTRICTED_NOT_ALLOWED: 'SIGNUP_RESTRICTED_NOT_ALLOWED',
|
||||||
|
SIGNUP_RESTRICTED: 'SIGNUP_RESTRICTED',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { ICashflowAccountTransactionsQuery, IPaginationMeta } from '@/interfaces
|
|||||||
@Service()
|
@Service()
|
||||||
export default class CashflowAccountTransactionsRepo {
|
export default class CashflowAccountTransactionsRepo {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the cashflow account transactions.
|
* Retrieve the cashflow account transactions.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const getDefaultPLQuery = (): IProfitLossSheetQuery => ({
|
|||||||
formatMoney: 'total',
|
formatMoney: 'total',
|
||||||
precision: 2,
|
precision: 2,
|
||||||
},
|
},
|
||||||
basis: 'accural',
|
basis: 'accrual',
|
||||||
|
|
||||||
noneZero: false,
|
noneZero: false,
|
||||||
noneTransactions: false,
|
noneTransactions: false,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default class TrialBalanceSheetService extends FinancialSheet {
|
|||||||
formatMoney: 'total',
|
formatMoney: 'total',
|
||||||
precision: 2,
|
precision: 2,
|
||||||
},
|
},
|
||||||
basis: 'accural',
|
basis: 'accrual',
|
||||||
noneZero: false,
|
noneZero: false,
|
||||||
noneTransactions: true,
|
noneTransactions: true,
|
||||||
onlyActive: false,
|
onlyActive: false,
|
||||||
|
|||||||
@@ -419,6 +419,58 @@ export const parseDate = (date: string) => {
|
|||||||
return date ? moment(date).utcOffset(0).format('YYYY-MM-DD') : '';
|
return date ? moment(date).utcOffset(0).format('YYYY-MM-DD') : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const nestedArrayToFlatten = (
|
||||||
|
collection,
|
||||||
|
property = 'children',
|
||||||
|
parseItem = (a, level) => a,
|
||||||
|
level = 1
|
||||||
|
) => {
|
||||||
|
const parseObject = (obj) =>
|
||||||
|
parseItem(
|
||||||
|
{
|
||||||
|
..._.omit(obj, [property]),
|
||||||
|
},
|
||||||
|
level
|
||||||
|
);
|
||||||
|
|
||||||
|
return collection.reduce((items, currentValue, index) => {
|
||||||
|
let localItems = [...items];
|
||||||
|
const parsedItem = parseObject(currentValue, level);
|
||||||
|
localItems.push(parsedItem);
|
||||||
|
|
||||||
|
if (Array.isArray(currentValue[property])) {
|
||||||
|
const flattenArray = nestedArrayToFlatten(
|
||||||
|
currentValue[property],
|
||||||
|
property,
|
||||||
|
parseItem,
|
||||||
|
level + 1
|
||||||
|
);
|
||||||
|
localItems = _.concat(localItems, flattenArray);
|
||||||
|
}
|
||||||
|
return localItems;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const assocDepthLevelToObjectTree = (
|
||||||
|
objects,
|
||||||
|
level = 1,
|
||||||
|
propertyName = 'level'
|
||||||
|
) => {
|
||||||
|
for (let i = 0; i < objects.length; i++) {
|
||||||
|
const object = objects[i];
|
||||||
|
object[propertyName] = level;
|
||||||
|
|
||||||
|
if (object.children) {
|
||||||
|
assocDepthLevelToObjectTree(object.children, level + 1, propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return objects;
|
||||||
|
};
|
||||||
|
|
||||||
|
const castCommaListEnvVarToArray = (envVar: string): Array<string> => {
|
||||||
|
return envVar ? envVar?.split(',')?.map(_.trim) : [];
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
templateRender,
|
templateRender,
|
||||||
accumSum,
|
accumSum,
|
||||||
@@ -449,4 +501,7 @@ export {
|
|||||||
dateRangeFromToCollection,
|
dateRangeFromToCollection,
|
||||||
transformToMapKeyValue,
|
transformToMapKeyValue,
|
||||||
mergeObjectsBykey,
|
mergeObjectsBykey,
|
||||||
|
nestedArrayToFlatten,
|
||||||
|
assocDepthLevelToObjectTree,
|
||||||
|
castCommaListEnvVarToArray
|
||||||
};
|
};
|
||||||
|
|||||||
17
packages/webapp/package-lock.json
generated
17
packages/webapp/package-lock.json
generated
@@ -1205,9 +1205,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@blueprintjs-formik/core": {
|
"@blueprintjs-formik/core": {
|
||||||
"version": "0.2.1",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/core/-/core-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/core/-/core-0.3.3.tgz",
|
||||||
"integrity": "sha512-YGJe+QorDGbkWDSUg6x69LYGN62Kgvb92Iz/voqmszVRKj4KcoPvd/7coF8Jmu+ZQE6LcwM/9ccB2i63L99ITA==",
|
"integrity": "sha512-ko7g54YSEcSq2K/GEpmiTG0foGLqe7DwgXGhkGxYEiHhLAUv8WvQmrFsm8e/KOW7n8mLGq0uaZVe2l8m3JTGGQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.keyby": "^4.6.0",
|
"lodash.keyby": "^4.6.0",
|
||||||
@@ -1227,9 +1227,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@blueprintjs-formik/select": {
|
"@blueprintjs-formik/select": {
|
||||||
"version": "0.1.5",
|
"version": "0.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/select/-/select-0.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/select/-/select-0.2.3.tgz",
|
||||||
"integrity": "sha512-EqGbuoiS1VrWpzjd39uVhBAmfVobdpgqalGcpODyGA+XAYoft1UU12yzTzrEOwBZpQKiC12UQwekUPspYBsVKA==",
|
"integrity": "sha512-j/zkX0B9wgtoHgK6Z/rlowB7F7zemrAajBU+d3caCoEYMMqwAI0XA++GytqrIhv5fEGjkZ1hkxS9j8eqX8vtjA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.keyby": "^4.6.0",
|
"lodash.keyby": "^4.6.0",
|
||||||
@@ -7298,6 +7298,11 @@
|
|||||||
"locate-path": "^3.0.0"
|
"locate-path": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flat": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="
|
||||||
|
},
|
||||||
"flat-cache": {
|
"flat-cache": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
"version": "1.7.1",
|
"version": "1.7.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blueprintjs-formik/core": "^0.2.1",
|
"@blueprintjs-formik/core": "^0.3.3",
|
||||||
"@blueprintjs-formik/datetime": "^0.3.4",
|
"@blueprintjs-formik/datetime": "^0.3.4",
|
||||||
"@blueprintjs-formik/select": "^0.1.4",
|
"@blueprintjs-formik/select": "^0.2.3",
|
||||||
"@blueprintjs/core": "^3.50.2",
|
"@blueprintjs/core": "^3.50.2",
|
||||||
"@blueprintjs/datetime": "^3.23.12",
|
"@blueprintjs/datetime": "^3.23.12",
|
||||||
"@blueprintjs/popover2": "^0.11.1",
|
"@blueprintjs/popover2": "^0.11.1",
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
"deepdash": "^5.3.9",
|
"deepdash": "^5.3.9",
|
||||||
"dependency-graph": "^0.11.0",
|
"dependency-graph": "^0.11.0",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"flat": "^5.0.2",
|
||||||
"formik": "^2.2.5",
|
"formik": "^2.2.5",
|
||||||
"http-proxy-middleware": "^1.0.0",
|
"http-proxy-middleware": "^1.0.0",
|
||||||
"jest": "24.9.0",
|
"jest": "24.9.0",
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { MenuItem } from '@blueprintjs/core';
|
|
||||||
import { FMultiSelect } from '../Forms';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { Classes } from '@blueprintjs/popover2';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {*} query
|
|
||||||
* @param {*} account
|
|
||||||
* @param {*} _index
|
|
||||||
* @param {*} exactMatch
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const accountItemPredicate = (query, account, _index, exactMatch) => {
|
|
||||||
const normalizedTitle = account.name.toLowerCase();
|
|
||||||
const normalizedQuery = query.toLowerCase();
|
|
||||||
|
|
||||||
if (exactMatch) {
|
|
||||||
return normalizedTitle === normalizedQuery;
|
|
||||||
} else {
|
|
||||||
return `${account.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {*} account
|
|
||||||
* @param {*} param1
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const accountItemRenderer = (
|
|
||||||
account,
|
|
||||||
{ handleClick, modifiers, query },
|
|
||||||
{ isSelected },
|
|
||||||
) => {
|
|
||||||
return (
|
|
||||||
<MenuItem
|
|
||||||
icon={isSelected ? 'tick' : 'blank'}
|
|
||||||
text={account.name}
|
|
||||||
label={account.code}
|
|
||||||
key={account.id}
|
|
||||||
onClick={handleClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const accountSelectProps = {
|
|
||||||
itemPredicate: accountItemPredicate,
|
|
||||||
itemRenderer: accountItemRenderer,
|
|
||||||
valueAccessor: (item) => item.id,
|
|
||||||
labelAccessor: (item) => item.code,
|
|
||||||
tagRenderer: (item) => item.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* branches mulit select.
|
|
||||||
* @param {*} param0
|
|
||||||
* @returns {JSX.Element}
|
|
||||||
*/
|
|
||||||
export function AccountMultiSelect({ accounts, ...rest }) {
|
|
||||||
return (
|
|
||||||
<FMultiSelect
|
|
||||||
items={accounts}
|
|
||||||
popoverProps={{
|
|
||||||
minimal: true,
|
|
||||||
}}
|
|
||||||
{...accountSelectProps}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,31 +1,97 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MenuItem } from '@blueprintjs/core';
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
import { MultiSelect } from '../MultiSelectTaggable';
|
import { FMultiSelect } from '../Forms';
|
||||||
|
import { accountPredicate } from './_components';
|
||||||
|
import { MenuItemNestedText } from '../Menu';
|
||||||
|
import { usePreprocessingAccounts } from './_hooks';
|
||||||
|
|
||||||
export function AccountsMultiSelect({ ...multiSelectProps }) {
|
// Create new account renderer.
|
||||||
return (
|
const createNewItemRenderer = (query, active, handleClick) => {
|
||||||
<MultiSelect
|
|
||||||
itemRenderer={(
|
|
||||||
item,
|
|
||||||
{ active, selected, handleClick, modifiers, query },
|
|
||||||
) => {
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
icon="add"
|
||||||
|
text={intl.get('list.create', { value: `"${query}"` })}
|
||||||
active={active}
|
active={active}
|
||||||
icon={selected ? 'tick' : 'blank'}
|
|
||||||
text={item.name}
|
|
||||||
label={item.code}
|
|
||||||
key={item.id}
|
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default account item renderer of the list.
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
const accountRenderer = (
|
||||||
|
item,
|
||||||
|
{ handleClick, modifiers, query },
|
||||||
|
{ isSelected },
|
||||||
|
) => {
|
||||||
|
if (!modifiers.matchesPredicate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
active={modifiers.active}
|
||||||
|
disabled={modifiers.disabled}
|
||||||
|
text={<MenuItemNestedText level={item.account_level} text={item.name} />}
|
||||||
|
key={item.id}
|
||||||
|
onClick={handleClick}
|
||||||
|
icon={isSelected ? 'tick' : 'blank'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create new item from the given query string.
|
||||||
|
const createNewItemFromQuery = (name) => ({ name });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts multi-select field binded with Formik form.
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
export function AccountsMultiSelect({
|
||||||
|
items,
|
||||||
|
allowCreate,
|
||||||
|
|
||||||
|
filterByRootTypes,
|
||||||
|
filterByParentTypes,
|
||||||
|
filterByTypes,
|
||||||
|
filterByNormal,
|
||||||
|
|
||||||
|
...rest
|
||||||
|
}) {
|
||||||
|
// Filters accounts based on filter props.
|
||||||
|
const filteredAccounts = usePreprocessingAccounts(items, {
|
||||||
|
filterByParentTypes,
|
||||||
|
filterByTypes,
|
||||||
|
filterByNormal,
|
||||||
|
filterByRootTypes,
|
||||||
|
});
|
||||||
|
// Maybe inject new item props to select component.
|
||||||
|
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||||
|
const maybeCreateNewItemFromQuery = allowCreate
|
||||||
|
? createNewItemFromQuery
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Handles the create item click.
|
||||||
|
const handleCreateItemClick = () => {
|
||||||
|
openDialog(DialogsName.AccountForm);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FMultiSelect
|
||||||
|
items={filteredAccounts}
|
||||||
|
valueAccessor={'id'}
|
||||||
|
textAccessor={'name'}
|
||||||
|
labelAccessor={'code'}
|
||||||
|
tagAccessor={'name'}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
fill={true}
|
itemPredicate={accountPredicate}
|
||||||
tagRenderer={(item) => item.name}
|
itemRenderer={accountRenderer}
|
||||||
resetOnSelect={true}
|
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||||
{...multiSelectProps}
|
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||||
|
onCreateItemSelect={handleCreateItemClick}
|
||||||
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
101
packages/webapp/src/components/Accounts/AccountsSelect.tsx
Normal file
101
packages/webapp/src/components/Accounts/AccountsSelect.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
|
import { MenuItemNestedText, FSelect } from '@/components';
|
||||||
|
import { accountPredicate } from './_components';
|
||||||
|
import { DialogsName } from '@/constants/dialogs';
|
||||||
|
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||||
|
import { usePreprocessingAccounts } from './_hooks';
|
||||||
|
|
||||||
|
// Create new account renderer.
|
||||||
|
const createNewItemRenderer = (query, active, handleClick) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
icon="add"
|
||||||
|
text={intl.get('list.create', { value: `"${query}"` })}
|
||||||
|
active={active}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create new item from the given query string.
|
||||||
|
const createNewItemFromQuery = (name) => ({ name });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default account item renderer.
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
const accountRenderer = (item, { handleClick, modifiers, query }) => {
|
||||||
|
if (!modifiers.matchesPredicate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
active={modifiers.active}
|
||||||
|
disabled={modifiers.disabled}
|
||||||
|
label={item.code}
|
||||||
|
key={item.id}
|
||||||
|
text={<MenuItemNestedText level={item.account_level} text={item.name} />}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts select field binded with Formik form.
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
function AccountsSelectRoot({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
|
items,
|
||||||
|
allowCreate,
|
||||||
|
|
||||||
|
filterByParentTypes,
|
||||||
|
filterByTypes,
|
||||||
|
filterByNormal,
|
||||||
|
filterByRootTypes,
|
||||||
|
|
||||||
|
...restProps
|
||||||
|
}) {
|
||||||
|
// Filters accounts based on filter props.
|
||||||
|
const filteredAccounts = usePreprocessingAccounts(items, {
|
||||||
|
filterByParentTypes,
|
||||||
|
filterByTypes,
|
||||||
|
filterByNormal,
|
||||||
|
filterByRootTypes,
|
||||||
|
});
|
||||||
|
// Maybe inject new item props to select component.
|
||||||
|
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||||
|
const maybeCreateNewItemFromQuery = allowCreate
|
||||||
|
? createNewItemFromQuery
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Handles the create item click.
|
||||||
|
const handleCreateItemClick = () => {
|
||||||
|
openDialog(DialogsName.AccountForm);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FSelect
|
||||||
|
items={filteredAccounts}
|
||||||
|
textAccessor={'name'}
|
||||||
|
labelAccessor={'code'}
|
||||||
|
valueAccessor={'id'}
|
||||||
|
popoverProps={{ minimal: true, usePortal: true, inline: false }}
|
||||||
|
itemPredicate={accountPredicate}
|
||||||
|
itemRenderer={accountRenderer}
|
||||||
|
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||||
|
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||||
|
onCreateItemSelect={handleCreateItemClick}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AccountsSelect = R.compose(withDialogActions)(AccountsSelectRoot);
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { MenuItem, Button } from '@blueprintjs/core';
|
|
||||||
import { Select } from '@blueprintjs/select';
|
|
||||||
import * as R from 'ramda';
|
|
||||||
|
|
||||||
import { MenuItemNestedText, FormattedMessage as T } from '@/components';
|
|
||||||
import { nestedArrayToflatten, filterAccountsByQuery } from '@/utils';
|
|
||||||
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
|
||||||
import { DialogsName } from '@/constants/dialogs';
|
|
||||||
|
|
||||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
|
||||||
|
|
||||||
// Create new account renderer.
|
|
||||||
const createNewItemRenderer = (query, active, handleClick) => {
|
|
||||||
return (
|
|
||||||
<MenuItem
|
|
||||||
icon="add"
|
|
||||||
text={intl.get('list.create', { value: `"${query}"` })}
|
|
||||||
active={active}
|
|
||||||
onClick={handleClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create new item from the given query string.
|
|
||||||
const createNewItemFromQuery = (name) => {
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filters accounts items.
|
|
||||||
const filterAccountsPredicater = (query, account, _index, exactMatch) => {
|
|
||||||
const normalizedTitle = account.name.toLowerCase();
|
|
||||||
const normalizedQuery = query.toLowerCase();
|
|
||||||
|
|
||||||
if (exactMatch) {
|
|
||||||
return normalizedTitle === normalizedQuery;
|
|
||||||
} else {
|
|
||||||
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accounts select list.
|
|
||||||
*/
|
|
||||||
function AccountsSelectListRoot({
|
|
||||||
// #withDialogActions
|
|
||||||
openDialog,
|
|
||||||
|
|
||||||
// #ownProps
|
|
||||||
accounts,
|
|
||||||
initialAccountId,
|
|
||||||
selectedAccountId,
|
|
||||||
defaultSelectText = 'Select account',
|
|
||||||
onAccountSelected,
|
|
||||||
disabled = false,
|
|
||||||
popoverFill = false,
|
|
||||||
|
|
||||||
filterByParentTypes,
|
|
||||||
filterByTypes,
|
|
||||||
filterByNormal,
|
|
||||||
filterByRootTypes,
|
|
||||||
|
|
||||||
allowCreate,
|
|
||||||
|
|
||||||
buttonProps = {},
|
|
||||||
}) {
|
|
||||||
const flattenAccounts = useMemo(
|
|
||||||
() => nestedArrayToflatten(accounts),
|
|
||||||
[accounts],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Filters accounts based on filter props.
|
|
||||||
const filteredAccounts = useMemo(() => {
|
|
||||||
let filteredAccounts = filterAccountsByQuery(flattenAccounts, {
|
|
||||||
filterByRootTypes,
|
|
||||||
filterByParentTypes,
|
|
||||||
filterByTypes,
|
|
||||||
filterByNormal,
|
|
||||||
});
|
|
||||||
return filteredAccounts;
|
|
||||||
}, [
|
|
||||||
flattenAccounts,
|
|
||||||
filterByRootTypes,
|
|
||||||
filterByParentTypes,
|
|
||||||
filterByTypes,
|
|
||||||
filterByNormal,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Find initial account object to set it as default account in initial render.
|
|
||||||
const initialAccount = useMemo(
|
|
||||||
() => filteredAccounts.find((a) => a.id === initialAccountId),
|
|
||||||
[initialAccountId, filteredAccounts],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Select account item.
|
|
||||||
const [selectedAccount, setSelectedAccount] = useState(
|
|
||||||
initialAccount || null,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof selectedAccountId !== 'undefined') {
|
|
||||||
const account = selectedAccountId
|
|
||||||
? filteredAccounts.find((a) => a.id === selectedAccountId)
|
|
||||||
: null;
|
|
||||||
setSelectedAccount(account);
|
|
||||||
}
|
|
||||||
}, [selectedAccountId, filteredAccounts, setSelectedAccount]);
|
|
||||||
|
|
||||||
// Account item of select accounts field.
|
|
||||||
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
|
|
||||||
return (
|
|
||||||
<MenuItem
|
|
||||||
text={<MenuItemNestedText level={item.level} text={item.name} />}
|
|
||||||
label={item.code}
|
|
||||||
key={item.id}
|
|
||||||
onClick={handleClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handle the account item select.
|
|
||||||
const handleAccountSelect = useCallback(
|
|
||||||
(account) => {
|
|
||||||
if (account.id) {
|
|
||||||
setSelectedAccount({ ...account });
|
|
||||||
onAccountSelected && onAccountSelected(account);
|
|
||||||
} else {
|
|
||||||
openDialog(DialogsName.AccountForm);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[setSelectedAccount, onAccountSelected, openDialog],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Maybe inject new item props to select component.
|
|
||||||
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
|
||||||
const maybeCreateNewItemFromQuery = allowCreate
|
|
||||||
? createNewItemFromQuery
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
items={filteredAccounts}
|
|
||||||
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
|
||||||
itemRenderer={accountItem}
|
|
||||||
itemPredicate={filterAccountsPredicater}
|
|
||||||
popoverProps={{
|
|
||||||
minimal: true,
|
|
||||||
usePortal: !popoverFill,
|
|
||||||
inline: popoverFill,
|
|
||||||
}}
|
|
||||||
filterable={true}
|
|
||||||
onItemSelect={handleAccountSelect}
|
|
||||||
disabled={disabled}
|
|
||||||
className={classNames('form-group--select-list', {
|
|
||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
|
||||||
})}
|
|
||||||
createNewItemRenderer={maybeCreateNewItemRenderer}
|
|
||||||
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
disabled={disabled}
|
|
||||||
text={selectedAccount ? selectedAccount.name : defaultSelectText}
|
|
||||||
{...buttonProps}
|
|
||||||
/>
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AccountsSelectList = R.compose(withDialogActions)(
|
|
||||||
AccountsSelectListRoot,
|
|
||||||
);
|
|
||||||
@@ -1,49 +1,15 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { useCallback } from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import { FSelect } from '@/components/Forms';
|
||||||
import { ListSelect } from '@/components';
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
|
||||||
|
|
||||||
export function AccountsTypesSelect({
|
|
||||||
accountsTypes,
|
|
||||||
selectedTypeId,
|
|
||||||
defaultSelectText = 'Select account type',
|
|
||||||
onTypeSelected,
|
|
||||||
disabled = false,
|
|
||||||
popoverFill = false,
|
|
||||||
...restProps
|
|
||||||
}) {
|
|
||||||
// Filters accounts types items.
|
|
||||||
const filterAccountTypeItems = (query, accountType, _index, exactMatch) => {
|
|
||||||
const normalizedTitle = accountType.label.toLowerCase();
|
|
||||||
const normalizedQuery = query.toLowerCase();
|
|
||||||
|
|
||||||
if (exactMatch) {
|
|
||||||
return normalizedTitle === normalizedQuery;
|
|
||||||
} else {
|
|
||||||
return normalizedTitle.indexOf(normalizedQuery) >= 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle item selected.
|
|
||||||
const handleItemSelected = (accountType) => {
|
|
||||||
onTypeSelected && onTypeSelected(accountType);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export function AccountsTypesSelect({ ...props }) {
|
||||||
return (
|
return (
|
||||||
<ListSelect
|
<FSelect
|
||||||
items={accountsTypes}
|
valueAccessor={'key'}
|
||||||
selectedItemProp={'key'}
|
labelAccessor={'label'}
|
||||||
selectedItem={selectedTypeId}
|
textAccessor={'label'}
|
||||||
textProp={'label'}
|
placeholder={'Select an account...'}
|
||||||
defaultText={defaultSelectText}
|
{...props}
|
||||||
onItemSelect={handleItemSelected}
|
|
||||||
itemPredicate={filterAccountTypeItems}
|
|
||||||
disabled={disabled}
|
|
||||||
className={classNames('form-group--select-list', {
|
|
||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
|
||||||
})}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
14
packages/webapp/src/components/Accounts/_components.tsx
Normal file
14
packages/webapp/src/components/Accounts/_components.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
// Filters accounts items.
|
||||||
|
export const accountPredicate = (query, account, _index, exactMatch) => {
|
||||||
|
const normalizedTitle = account.name.toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
if (exactMatch) {
|
||||||
|
return normalizedTitle === normalizedQuery;
|
||||||
|
} else {
|
||||||
|
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
36
packages/webapp/src/components/Accounts/_hooks.ts
Normal file
36
packages/webapp/src/components/Accounts/_hooks.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { filterAccountsByQuery, nestedArrayToflatten } from '@/utils';
|
||||||
|
|
||||||
|
interface PreprocessingAccountsOptions {
|
||||||
|
filterByRootTypes: string[];
|
||||||
|
filterByParentTypes: string[];
|
||||||
|
filterByTypes: string[];
|
||||||
|
filterByNormal: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePreprocessingAccounts = (
|
||||||
|
items: any,
|
||||||
|
{
|
||||||
|
filterByRootTypes,
|
||||||
|
filterByParentTypes,
|
||||||
|
filterByTypes,
|
||||||
|
filterByNormal,
|
||||||
|
}: PreprocessingAccountsOptions,
|
||||||
|
) => {
|
||||||
|
return useMemo(() => {
|
||||||
|
const flattenAccounts = nestedArrayToflatten(items);
|
||||||
|
const filteredAccounts = filterAccountsByQuery(flattenAccounts, {
|
||||||
|
filterByRootTypes,
|
||||||
|
filterByParentTypes,
|
||||||
|
filterByTypes,
|
||||||
|
filterByNormal,
|
||||||
|
});
|
||||||
|
return filteredAccounts;
|
||||||
|
}, [
|
||||||
|
items,
|
||||||
|
filterByRootTypes,
|
||||||
|
filterByParentTypes,
|
||||||
|
filterByTypes,
|
||||||
|
filterByNormal,
|
||||||
|
]);
|
||||||
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
export * from './AccountMultiSelect';
|
export * from './AccountsSelect';
|
||||||
export * from './AccountsMultiSelect';
|
export * from './AccountsMultiSelect';
|
||||||
export * from './AccountsSelectList';
|
|
||||||
export * from './AccountsSuggestField';
|
export * from './AccountsSuggestField';
|
||||||
export * from './AccountsTypesSelect';
|
export * from './AccountsTypesSelect';
|
||||||
|
|||||||
@@ -77,11 +77,13 @@ export function BankAccount({
|
|||||||
</BankAccountHeader>
|
</BankAccountHeader>
|
||||||
|
|
||||||
<BankAccountMeta>
|
<BankAccountMeta>
|
||||||
|
{false && (
|
||||||
<BankAccountMetaLine
|
<BankAccountMetaLine
|
||||||
title={intl.get('cash_flow.label_account_transcations')}
|
title={intl.get('cash_flow.transactions_for_review')}
|
||||||
value={2}
|
value={'0'}
|
||||||
className={clsx({ [Classes.SKELETON]: loading })}
|
className={clsx({ [Classes.SKELETON]: loading })}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<BankAccountMetaLine
|
<BankAccountMetaLine
|
||||||
title={updatedBeforeText}
|
title={updatedBeforeText}
|
||||||
className={clsx({ [Classes.SKELETON]: loading })}
|
className={clsx({ [Classes.SKELETON]: loading })}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
import { MenuItem, Button } from '@blueprintjs/core';
|
|
||||||
import { FSelect } from '../Forms';
|
import { FSelect } from '../Forms';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,30 +27,6 @@ const currencyItemPredicate = (query, currency, _index, exactMatch) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {*} currency
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const currencyItemRenderer = (currency, { handleClick, modifiers, query }) => {
|
|
||||||
return (
|
|
||||||
<MenuItem
|
|
||||||
active={modifiers.active}
|
|
||||||
disabled={modifiers.disabled}
|
|
||||||
text={currency.currency_name}
|
|
||||||
label={currency.currency_code.toString()}
|
|
||||||
key={currency.id}
|
|
||||||
onClick={handleClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const currencySelectProps = {
|
|
||||||
itemPredicate: currencyItemPredicate,
|
|
||||||
itemRenderer: currencyItemRenderer,
|
|
||||||
valueAccessor: 'currency_code',
|
|
||||||
labelAccessor: 'currency_code',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {*} currencies
|
* @param {*} currencies
|
||||||
@@ -60,18 +35,13 @@ const currencySelectProps = {
|
|||||||
export function CurrencySelect({ currencies, ...rest }) {
|
export function CurrencySelect({ currencies, ...rest }) {
|
||||||
return (
|
return (
|
||||||
<FSelect
|
<FSelect
|
||||||
{...currencySelectProps}
|
itemPredicate={currencyItemPredicate}
|
||||||
|
valueAccessor={'currency_code'}
|
||||||
|
textAccessor={'currency_name'}
|
||||||
|
labelAccessor={'currency_code'}
|
||||||
{...rest}
|
{...rest}
|
||||||
items={currencies}
|
items={currencies}
|
||||||
input={CurrnecySelectButton}
|
placeholder={intl.get('select_currency_code')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {*} label
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function CurrnecySelectButton({ label }) {
|
|
||||||
return <Button text={label ? label : intl.get('select_currency_code')} />;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
FormGroup,
|
FormGroup,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
@@ -9,8 +10,9 @@ import {
|
|||||||
EditableText,
|
EditableText,
|
||||||
TextArea,
|
TextArea,
|
||||||
} from '@blueprintjs-formik/core';
|
} from '@blueprintjs-formik/core';
|
||||||
import { Select, MultiSelect } from '@blueprintjs-formik/select';
|
import { MultiSelect } from '@blueprintjs-formik/select';
|
||||||
import { DateInput } from '@blueprintjs-formik/datetime';
|
import { DateInput } from '@blueprintjs-formik/datetime';
|
||||||
|
import { FSelect } from './Select';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
FormGroup as FFormGroup,
|
FormGroup as FFormGroup,
|
||||||
@@ -19,7 +21,7 @@ export {
|
|||||||
Checkbox as FCheckbox,
|
Checkbox as FCheckbox,
|
||||||
RadioGroup as FRadioGroup,
|
RadioGroup as FRadioGroup,
|
||||||
Switch as FSwitch,
|
Switch as FSwitch,
|
||||||
Select as FSelect,
|
FSelect,
|
||||||
MultiSelect as FMultiSelect,
|
MultiSelect as FMultiSelect,
|
||||||
EditableText as FEditableText,
|
EditableText as FEditableText,
|
||||||
TextArea as FTextArea,
|
TextArea as FTextArea,
|
||||||
|
|||||||
58
packages/webapp/src/components/Forms/Select.tsx
Normal file
58
packages/webapp/src/components/Forms/Select.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { Button } from '@blueprintjs/core';
|
||||||
|
import { Select } from '@blueprintjs-formik/select';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import clsx from 'classnames';
|
||||||
|
|
||||||
|
export function FSelect({ ...props }) {
|
||||||
|
const input = ({ activeItem, text, label, value }) => {
|
||||||
|
return (
|
||||||
|
<SelectButton
|
||||||
|
text={text || props.placeholder || 'Select an item ...'}
|
||||||
|
disabled={props.disabled || false}
|
||||||
|
{...props.buttonProps}
|
||||||
|
className={clsx({ 'is-selected': !!text }, props.className)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return <Select input={input} {...props} fill={true} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectButton = styled(Button)`
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 transparent;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
position: relative;
|
||||||
|
padding-right: 30px;
|
||||||
|
|
||||||
|
&:not(.is-selected):not([class*='bp3-intent-']):not(.bp3-minimal) {
|
||||||
|
color: #5c7080;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
border-top: 5px solid #8d8d8d;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -2px;
|
||||||
|
margin-right: 12px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
&:not([class*='bp3-intent-']):not(.bp3-disabled) {
|
||||||
|
&,
|
||||||
|
&:hover {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bp3-intent-danger & {
|
||||||
|
border-color: #db3737;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React, { createContext } from 'react';
|
||||||
|
import { useAuthMetadata } from '@/hooks/query';
|
||||||
|
import { Spinner } from '@blueprintjs/core';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const AuthMetaBootContext = createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boots the authentication page metadata.
|
||||||
|
*/
|
||||||
|
function AuthMetaBootProvider({ ...props }) {
|
||||||
|
const { isLoading: isAuthMetaLoading, data: authMeta } = useAuthMetadata();
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
isAuthMetaLoading,
|
||||||
|
signupDisabled: authMeta?.meta?.signup_disabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isAuthMetaLoading) {
|
||||||
|
return (
|
||||||
|
<SpinnerRoot>
|
||||||
|
<Spinner size={30} value={null} />
|
||||||
|
</SpinnerRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <AuthMetaBootContext.Provider value={state} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useAuthMetaBoot = () => React.useContext(AuthMetaBootContext);
|
||||||
|
|
||||||
|
export { AuthMetaBootContext, AuthMetaBootProvider, useAuthMetaBoot };
|
||||||
|
|
||||||
|
const SpinnerRoot = styled.div`
|
||||||
|
margin-top: 5rem;
|
||||||
|
`;
|
||||||
@@ -10,12 +10,11 @@ import { Icon, FormattedMessage as T } from '@/components';
|
|||||||
import { useIsAuthenticated } from '@/hooks/state';
|
import { useIsAuthenticated } from '@/hooks/state';
|
||||||
|
|
||||||
import '@/style/pages/Authentication/Auth.scss';
|
import '@/style/pages/Authentication/Auth.scss';
|
||||||
|
import { AuthMetaBootProvider } from './AuthMetaBoot';
|
||||||
|
|
||||||
export function Authentication() {
|
export function Authentication() {
|
||||||
const to = { pathname: '/' };
|
const to = { pathname: '/' };
|
||||||
const location = useLocation();
|
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const isAuthenticated = useIsAuthenticated();
|
||||||
const locationKey = location.pathname;
|
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
return <Redirect to={to} />;
|
return <Redirect to={to} />;
|
||||||
@@ -28,6 +27,20 @@ export function Authentication() {
|
|||||||
<Icon icon="bigcapital" height={37} width={214} />
|
<Icon icon="bigcapital" height={37} width={214} />
|
||||||
</AuthLogo>
|
</AuthLogo>
|
||||||
|
|
||||||
|
<AuthMetaBootProvider>
|
||||||
|
<AuthenticationRoutes />
|
||||||
|
</AuthMetaBootProvider>
|
||||||
|
</AuthInsider>
|
||||||
|
</AuthPage>
|
||||||
|
</BodyClassName>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AuthenticationRoutes() {
|
||||||
|
const location = useLocation();
|
||||||
|
const locationKey = location.pathname;
|
||||||
|
|
||||||
|
return (
|
||||||
<TransitionGroup>
|
<TransitionGroup>
|
||||||
<CSSTransition
|
<CSSTransition
|
||||||
timeout={500}
|
timeout={500}
|
||||||
@@ -46,9 +59,6 @@ export function Authentication() {
|
|||||||
</Switch>
|
</Switch>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
</AuthInsider>
|
|
||||||
</AuthPage>
|
|
||||||
</BodyClassName>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ import {
|
|||||||
AuthFooterLink,
|
AuthFooterLink,
|
||||||
AuthInsiderCard,
|
AuthInsiderCard,
|
||||||
} from './_components';
|
} from './_components';
|
||||||
|
import { useAuthMetaBoot } from './AuthMetaBoot';
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
crediential: '',
|
crediential: '',
|
||||||
password: '',
|
password: '',
|
||||||
keepLoggedIn: false
|
keepLoggedIn: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,12 +65,15 @@ export default function Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function LoginFooterLinks() {
|
function LoginFooterLinks() {
|
||||||
|
const { signupDisabled } = useAuthMetaBoot();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthFooterLinks>
|
<AuthFooterLinks>
|
||||||
|
{!signupDisabled && (
|
||||||
<AuthFooterLink>
|
<AuthFooterLink>
|
||||||
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
|
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
|
||||||
</AuthFooterLink>
|
</AuthFooterLink>
|
||||||
|
)}
|
||||||
<AuthFooterLink>
|
<AuthFooterLink>
|
||||||
<Link to={'/auth/send_reset_password'}>
|
<Link to={'/auth/send_reset_password'}>
|
||||||
<T id={'forget_my_password'} />
|
<T id={'forget_my_password'} />
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import AuthInsider from '@/containers/Authentication/AuthInsider';
|
|||||||
import { useAuthLogin, useAuthRegister } from '@/hooks/query/authentication';
|
import { useAuthLogin, useAuthRegister } from '@/hooks/query/authentication';
|
||||||
|
|
||||||
import RegisterForm from './RegisterForm';
|
import RegisterForm from './RegisterForm';
|
||||||
import { RegisterSchema, transformRegisterErrorsToForm } from './utils';
|
import { RegisterSchema, transformRegisterErrorsToForm, transformRegisterToastMessages } from './utils';
|
||||||
import {
|
import {
|
||||||
AuthFooterLinks,
|
AuthFooterLinks,
|
||||||
AuthFooterLink,
|
AuthFooterLink,
|
||||||
@@ -57,7 +57,11 @@ export default function RegisterUserForm() {
|
|||||||
},
|
},
|
||||||
}) => {
|
}) => {
|
||||||
const formErrors = transformRegisterErrorsToForm(errors);
|
const formErrors = transformRegisterErrorsToForm(errors);
|
||||||
|
const toastMessages = transformRegisterToastMessages(errors);
|
||||||
|
|
||||||
|
toastMessages.forEach((toastMessage) => {
|
||||||
|
AppToaster.show(toastMessage);
|
||||||
|
});
|
||||||
setErrors(formErrors);
|
setErrors(formErrors);
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
} from './_components';
|
} from './_components';
|
||||||
import ResetPasswordForm from './ResetPasswordForm';
|
import ResetPasswordForm from './ResetPasswordForm';
|
||||||
import { ResetPasswordSchema } from './utils';
|
import { ResetPasswordSchema } from './utils';
|
||||||
|
import { useAuthMetaBoot } from './AuthMetaBoot';
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
password: '',
|
password: '',
|
||||||
@@ -79,12 +80,15 @@ export default function ResetPassword() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ResetPasswordFooterLinks() {
|
function ResetPasswordFooterLinks() {
|
||||||
|
const { signupDisabled } = useAuthMetaBoot();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthFooterLinks>
|
<AuthFooterLinks>
|
||||||
|
{!signupDisabled && (
|
||||||
<AuthFooterLink>
|
<AuthFooterLink>
|
||||||
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
|
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
|
||||||
</AuthFooterLink>
|
</AuthFooterLink>
|
||||||
|
)}
|
||||||
<AuthFooterLink>
|
<AuthFooterLink>
|
||||||
Return to <Link to={'/auth/login'}>Sign In</Link>
|
Return to <Link to={'/auth/login'}>Sign In</Link>
|
||||||
</AuthFooterLink>
|
</AuthFooterLink>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
transformSendResetPassErrorsToToasts,
|
transformSendResetPassErrorsToToasts,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import AuthInsider from '@/containers/Authentication/AuthInsider';
|
import AuthInsider from '@/containers/Authentication/AuthInsider';
|
||||||
|
import { useAuthMetaBoot } from './AuthMetaBoot';
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
crediential: '',
|
crediential: '',
|
||||||
@@ -27,7 +28,7 @@ const initialValues = {
|
|||||||
/**
|
/**
|
||||||
* Send reset password page.
|
* Send reset password page.
|
||||||
*/
|
*/
|
||||||
export default function SendResetPassword({ requestSendResetPassword }) {
|
export default function SendResetPassword() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { mutateAsync: sendResetPasswordMutate } = useAuthSendResetPassword();
|
const { mutateAsync: sendResetPasswordMutate } = useAuthSendResetPassword();
|
||||||
|
|
||||||
@@ -75,12 +76,15 @@ export default function SendResetPassword({ requestSendResetPassword }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SendResetPasswordFooterLinks() {
|
function SendResetPasswordFooterLinks() {
|
||||||
|
const { signupDisabled } = useAuthMetaBoot();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthFooterLinks>
|
<AuthFooterLinks>
|
||||||
|
{!signupDisabled && (
|
||||||
<AuthFooterLink>
|
<AuthFooterLink>
|
||||||
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
|
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
|
||||||
</AuthFooterLink>
|
</AuthFooterLink>
|
||||||
|
)}
|
||||||
<AuthFooterLink>
|
<AuthFooterLink>
|
||||||
Return to <Link to={'/auth/login'}>Sign In</Link>
|
Return to <Link to={'/auth/login'}>Sign In</Link>
|
||||||
</AuthFooterLink>
|
</AuthFooterLink>
|
||||||
|
|||||||
@@ -94,3 +94,21 @@ export const transformRegisterErrorsToForm = (errors) => {
|
|||||||
}
|
}
|
||||||
return formErrors;
|
return formErrors;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const transformRegisterToastMessages = (errors) => {
|
||||||
|
const toastErrors = [];
|
||||||
|
|
||||||
|
if (errors.some((e) => e.type === 'SIGNUP_RESTRICTED_NOT_ALLOWED')) {
|
||||||
|
toastErrors.push({
|
||||||
|
message:
|
||||||
|
'The sign-up is restricted, the given email address is not allowed to sign-up.',
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
} else if (errors.find((e) => e.type === 'SIGNUP_RESTRICTED')) {
|
||||||
|
toastErrors.push({
|
||||||
|
message: 'Sign-up is disabled, and no new accounts can be created.',
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return toastErrors;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,32 +1,29 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import { Form, useFormikContext } from 'formik';
|
||||||
import { Form, FastField, Field, ErrorMessage, useFormikContext } from 'formik';
|
import { Button, Classes, FormGroup, Intent } from '@blueprintjs/core';
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Classes,
|
|
||||||
FormGroup,
|
|
||||||
InputGroup,
|
|
||||||
Intent,
|
|
||||||
TextArea,
|
|
||||||
Checkbox,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import {
|
import {
|
||||||
If,
|
If,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
Hint,
|
Hint,
|
||||||
AccountsSelectList,
|
AccountsSelect,
|
||||||
AccountsTypesSelect,
|
AccountsTypesSelect,
|
||||||
CurrencySelect,
|
CurrencySelect,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
|
FFormGroup,
|
||||||
|
FInputGroup,
|
||||||
|
FCheckbox,
|
||||||
|
FTextArea,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import withAccounts from '@/containers/Accounts/withAccounts';
|
import withAccounts from '@/containers/Accounts/withAccounts';
|
||||||
|
|
||||||
import { inputIntent, compose } from '@/utils';
|
|
||||||
import { useAutofocus } from '@/hooks';
|
|
||||||
import { FOREIGN_CURRENCY_ACCOUNTS } from '@/constants/accountTypes';
|
import { FOREIGN_CURRENCY_ACCOUNTS } from '@/constants/accountTypes';
|
||||||
|
|
||||||
|
import { useAutofocus } from '@/hooks';
|
||||||
import { useAccountDialogContext } from './AccountDialogProvider';
|
import { useAccountDialogContext } from './AccountDialogProvider';
|
||||||
|
|
||||||
import { parentAccountShouldUpdate } from './utils';
|
import { parentAccountShouldUpdate } from './utils';
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account form dialogs fields.
|
* Account form dialogs fields.
|
||||||
@@ -36,7 +33,7 @@ function AccountFormDialogFields({
|
|||||||
onClose,
|
onClose,
|
||||||
action,
|
action,
|
||||||
}) {
|
}) {
|
||||||
const { values, isSubmitting } = useFormikContext();
|
const { values, isSubmitting, setFieldValue } = useFormikContext();
|
||||||
const accountNameFieldRef = useAutofocus();
|
const accountNameFieldRef = useAutofocus();
|
||||||
|
|
||||||
// Account form context.
|
// Account form context.
|
||||||
@@ -46,146 +43,120 @@ function AccountFormDialogFields({
|
|||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<div className={Classes.DIALOG_BODY}>
|
<div className={Classes.DIALOG_BODY}>
|
||||||
<Field name={'account_type'}>
|
<FFormGroup
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
inline={true}
|
||||||
<FormGroup
|
|
||||||
label={<T id={'account_type'} />}
|
label={<T id={'account_type'} />}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
className={classNames('form-group--account-type', Classes.FILL)}
|
name={'account_type'}
|
||||||
inline={true}
|
fastField={true}
|
||||||
helperText={<ErrorMessage name="account_type" />}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
>
|
>
|
||||||
<AccountsTypesSelect
|
<AccountsTypesSelect
|
||||||
accountsTypes={accountsTypes}
|
name={'account_type'}
|
||||||
selectedTypeId={value}
|
items={accountsTypes}
|
||||||
defaultSelectText={<T id={'select_account_type'} />}
|
onItemSelect={(accountType) => {
|
||||||
onTypeSelected={(accountType) => {
|
setFieldValue('account_type', accountType.key);
|
||||||
form.setFieldValue('account_type', accountType.key);
|
setFieldValue('currency_code', '');
|
||||||
form.setFieldValue('currency_code', '');
|
|
||||||
}}
|
}}
|
||||||
disabled={fieldsDisabled.accountType}
|
disabled={fieldsDisabled.accountType}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
popoverFill={true}
|
fastField={true}
|
||||||
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<FastField name={'name'}>
|
<FFormGroup
|
||||||
{({ field, meta: { error, touched } }) => (
|
name={'name'}
|
||||||
<FormGroup
|
|
||||||
label={<T id={'account_name'} />}
|
label={<T id={'account_name'} />}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
className={'form-group--account-name'}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name="name" />}
|
|
||||||
inline={true}
|
inline={true}
|
||||||
|
fastField={true}
|
||||||
>
|
>
|
||||||
<InputGroup
|
<FInputGroup
|
||||||
medium={true}
|
medium={true}
|
||||||
inputRef={(ref) => (accountNameFieldRef.current = ref)}
|
inputRef={(ref) => (accountNameFieldRef.current = ref)}
|
||||||
{...field}
|
name={'name'}
|
||||||
|
fastField={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
<FastField name={'code'}>
|
<FFormGroup
|
||||||
{({ form, field, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'account_code'} />}
|
label={<T id={'account_code'} />}
|
||||||
className={'form-group--account-code'}
|
name={'code'}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name="code" />}
|
|
||||||
inline={true}
|
|
||||||
labelInfo={<Hint content={<T id="account_code_hint" />} />}
|
labelInfo={<Hint content={<T id="account_code_hint" />} />}
|
||||||
>
|
|
||||||
<InputGroup medium={true} {...field} />
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
<Field name={'subaccount'} type={'checkbox'}>
|
|
||||||
{({ field, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={' '}
|
|
||||||
className={classNames('form-group--subaccount')}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
inline={true}
|
inline={true}
|
||||||
|
fastField={true}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<FInputGroup medium={true} name={'code'} fastField={true} />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
label={' '}
|
||||||
|
name={'subaccount'}
|
||||||
|
inline={true}
|
||||||
|
fastField={true}
|
||||||
|
>
|
||||||
|
<FCheckbox
|
||||||
inline={true}
|
inline={true}
|
||||||
label={<T id={'sub_account'} />}
|
label={<T id={'sub_account'} />}
|
||||||
name={'subaccount'}
|
name={'subaccount'}
|
||||||
{...field}
|
fastField={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<FastField
|
{values.subaccount && (
|
||||||
|
<FFormGroup
|
||||||
name={'parent_account_id'}
|
name={'parent_account_id'}
|
||||||
shouldUpdate={parentAccountShouldUpdate}
|
shouldUpdate={parentAccountShouldUpdate}
|
||||||
>
|
|
||||||
{({
|
|
||||||
form: { values, setFieldValue },
|
|
||||||
field: { value },
|
|
||||||
meta: { error, touched },
|
|
||||||
}) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'parent_account'} />}
|
label={<T id={'parent_account'} />}
|
||||||
className={classNames('form-group--parent-account', Classes.FILL)}
|
|
||||||
inline={true}
|
inline={true}
|
||||||
intent={inputIntent({ error, touched })}
|
fastField={true}
|
||||||
helperText={<ErrorMessage name="parent_account_id" />}
|
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
name={'parent_account_id'}
|
||||||
onAccountSelected={(account) => {
|
items={accounts}
|
||||||
setFieldValue('parent_account_id', account.id);
|
shouldUpdate={parentAccountShouldUpdate}
|
||||||
}}
|
placeholder={<T id={'select_parent_account'} />}
|
||||||
defaultSelectText={<T id={'select_parent_account'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
popoverFill={true}
|
|
||||||
filterByTypes={values.account_type}
|
filterByTypes={values.account_type}
|
||||||
disabled={!values.subaccount}
|
buttonProps={{ disabled: !values.subaccount }}
|
||||||
|
fastField={true}
|
||||||
|
fill={true}
|
||||||
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
|
||||||
|
|
||||||
<If condition={FOREIGN_CURRENCY_ACCOUNTS.includes(values.account_type)}>
|
<If condition={FOREIGN_CURRENCY_ACCOUNTS.includes(values.account_type)}>
|
||||||
{/*------------ Currency -----------*/}
|
{/*------------ Currency -----------*/}
|
||||||
<FastField name={'currency_code'}>
|
<FFormGroup
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'currency'} />}
|
label={<T id={'currency'} />}
|
||||||
className={classNames('form-group--select-list', Classes.FILL)}
|
name={'currency_code'}
|
||||||
inline={true}
|
inline={true}
|
||||||
|
fastField={true}
|
||||||
>
|
>
|
||||||
<CurrencySelect
|
<CurrencySelect
|
||||||
name={'currency_code'}
|
name={'currency_code'}
|
||||||
currencies={currencies}
|
currencies={currencies}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
|
fastField={true}
|
||||||
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
</If>
|
</If>
|
||||||
<FastField name={'description'}>
|
|
||||||
{({ field, meta: { error, touched } }) => (
|
<FFormGroup
|
||||||
<FormGroup
|
|
||||||
label={<T id={'description'} />}
|
label={<T id={'description'} />}
|
||||||
className={'form-group--description'}
|
name={'description'}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name={'description'} />}
|
|
||||||
inline={true}
|
inline={true}
|
||||||
|
fastField={true}
|
||||||
>
|
>
|
||||||
<TextArea growVertically={true} height={280} {...field} />
|
<FTextArea
|
||||||
</FormGroup>
|
name={'description'}
|
||||||
)}
|
growVertically={true}
|
||||||
</FastField>
|
height={280}
|
||||||
|
fill={true}
|
||||||
|
fastField={true}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ import { customersFieldShouldUpdate, accountsFieldShouldUpdate } from './utils';
|
|||||||
import {
|
import {
|
||||||
CurrencySelectList,
|
CurrencySelectList,
|
||||||
CustomerSelectField,
|
CustomerSelectField,
|
||||||
AccountsSelectList,
|
FFormGroup,
|
||||||
|
AccountsSelect,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
Hint,
|
Hint,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
@@ -54,37 +55,26 @@ export default function ExpenseFormHeader() {
|
|||||||
)}
|
)}
|
||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
<FastField
|
<FFormGroup
|
||||||
name={'payment_account_id'}
|
name={'payment_account_id'}
|
||||||
accounts={accounts}
|
items={accounts}
|
||||||
|
label={<T id={'payment_account'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
inline={true}
|
||||||
|
fastField={true}
|
||||||
shouldUpdate={accountsFieldShouldUpdate}
|
shouldUpdate={accountsFieldShouldUpdate}
|
||||||
>
|
>
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
<AccountsSelect
|
||||||
<FormGroup
|
name={'payment_account_id'}
|
||||||
label={<T id={'payment_account'} />}
|
items={accounts}
|
||||||
className={classNames(
|
placeholder={<T id={'select_payment_account'} />}
|
||||||
'form-group--payment_account',
|
|
||||||
'form-group--select-list',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
labelInfo={<FieldRequiredHint />}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name={'payment_account_id'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<AccountsSelectList
|
|
||||||
accounts={accounts}
|
|
||||||
onAccountSelected={(account) => {
|
|
||||||
form.setFieldValue('payment_account_id', account.id);
|
|
||||||
}}
|
|
||||||
defaultSelectText={<T id={'select_payment_account'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
|
filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
|
||||||
allowCreate={true}
|
allowCreate={true}
|
||||||
|
fastField={true}
|
||||||
|
shouldUpdate={accountsFieldShouldUpdate}
|
||||||
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
<FastField name={'currency_code'}>
|
<FastField name={'currency_code'}>
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export const customersFieldShouldUpdate = (newProps, oldProps) => {
|
|||||||
*/
|
*/
|
||||||
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
|
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
|
||||||
return (
|
return (
|
||||||
newProps.accounts !== oldProps.accounts ||
|
newProps.items !== oldProps.items ||
|
||||||
defaultFastFieldShouldUpdate(newProps, oldProps)
|
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import { Classes } from '@blueprintjs/core';
|
import { Classes } from '@blueprintjs/core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AccountMultiSelect,
|
AccountsMultiSelect,
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
@@ -54,7 +54,7 @@ function GLHeaderGeneralPaneContent() {
|
|||||||
name={'accountsIds'}
|
name={'accountsIds'}
|
||||||
className={Classes.FILL}
|
className={Classes.FILL}
|
||||||
>
|
>
|
||||||
<AccountMultiSelect name="accountsIds" accounts={accounts} />
|
<AccountsMultiSelect name="accountsIds" items={accounts} />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const getDefaultGeneralLedgerQuery = () => {
|
|||||||
return {
|
return {
|
||||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||||
basis: 'accural',
|
basis: 'accrual',
|
||||||
filterByOption: 'with-transactions',
|
filterByOption: 'with-transactions',
|
||||||
branchesIds: [],
|
branchesIds: [],
|
||||||
accountsIds: [],
|
accountsIds: [],
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const getDefaultJournalQuery = () => {
|
|||||||
return {
|
return {
|
||||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||||
basis: 'accural',
|
basis: 'accrual',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default function RadiosAccountingBasis(props) {
|
|||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<Radio label={intl.get('cash')} value="cash" />
|
<Radio label={intl.get('cash')} value="cash" />
|
||||||
<Radio label={intl.get('accrual')} value="accural" />
|
<Radio label={intl.get('accrual')} value="accrual" />
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</FastField>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function getDefaultTrialBalanceQuery() {
|
|||||||
return {
|
return {
|
||||||
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
fromDate: moment().startOf('year').format('YYYY-MM-DD'),
|
||||||
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
toDate: moment().endOf('year').format('YYYY-MM-DD'),
|
||||||
basis: 'accural',
|
basis: 'accrual',
|
||||||
filterByOption: 'with-transactions',
|
filterByOption: 'with-transactions',
|
||||||
branchesIds: [],
|
branchesIds: [],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ import {
|
|||||||
ControlGroup,
|
ControlGroup,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import {
|
import {
|
||||||
AccountsSelectList,
|
AccountsSelect,
|
||||||
MoneyInputGroup,
|
MoneyInputGroup,
|
||||||
Col,
|
Col,
|
||||||
Row,
|
Row,
|
||||||
Hint,
|
Hint,
|
||||||
InputPrependText,
|
InputPrependText,
|
||||||
|
FFormGroup,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { FormattedMessage as T } from '@/components';
|
import { FormattedMessage as T } from '@/components';
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import { useItemFormContext } from './ItemFormProvider';
|
import { useItemFormContext } from './ItemFormProvider';
|
||||||
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
|
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
|
||||||
@@ -91,42 +91,29 @@ function ItemFormBody({ organization: { base_currency } }) {
|
|||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
{/*------------- Selling account ------------- */}
|
{/*------------- Selling account ------------- */}
|
||||||
<FastField
|
<FFormGroup
|
||||||
name={'sell_account_id'}
|
|
||||||
sellable={values.sellable}
|
|
||||||
accounts={accounts}
|
|
||||||
shouldUpdate={sellAccountFieldShouldUpdate}
|
|
||||||
>
|
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'account'} />}
|
label={<T id={'account'} />}
|
||||||
|
name={'sell_account_id'}
|
||||||
labelInfo={
|
labelInfo={
|
||||||
<Hint content={<T id={'item.field.sell_account.hint'} />} />
|
<Hint content={<T id={'item.field.sell_account.hint'} />} />
|
||||||
}
|
}
|
||||||
inline={true}
|
inline={true}
|
||||||
intent={inputIntent({ error, touched })}
|
items={accounts}
|
||||||
helperText={<ErrorMessage name="sell_account_id" />}
|
sellable={values.sellable}
|
||||||
className={classNames(
|
shouldUpdate={sellAccountFieldShouldUpdate}
|
||||||
'form-group--sell-account',
|
fastField={true}
|
||||||
'form-group--select-list',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
name={'sell_account_id'}
|
||||||
onAccountSelected={(account) => {
|
items={accounts}
|
||||||
form.setFieldValue('sell_account_id', account.id);
|
placeholder={<T id={'select_account'} />}
|
||||||
}}
|
disabled={!values.sellable}
|
||||||
defaultSelectText={<T id={'select_account'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
disabled={!form.values.sellable}
|
|
||||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
|
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
|
||||||
popoverFill={true}
|
fill={true}
|
||||||
allowCreate={true}
|
allowCreate={true}
|
||||||
|
fastField={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
<FastField
|
<FastField
|
||||||
name={'sell_description'}
|
name={'sell_description'}
|
||||||
@@ -200,42 +187,31 @@ function ItemFormBody({ organization: { base_currency } }) {
|
|||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
{/*------------- Cost account ------------- */}
|
{/*------------- Cost account ------------- */}
|
||||||
<FastField
|
<FFormGroup
|
||||||
name={'cost_account_id'}
|
name={'cost_account_id'}
|
||||||
purchasable={values.purchasable}
|
purchasable={values.purchasable}
|
||||||
accounts={accounts}
|
items={accounts}
|
||||||
shouldUpdate={costAccountFieldShouldUpdate}
|
shouldUpdate={costAccountFieldShouldUpdate}
|
||||||
>
|
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'account'} />}
|
label={<T id={'account'} />}
|
||||||
labelInfo={
|
labelInfo={
|
||||||
<Hint content={<T id={'item.field.cost_account.hint'} />} />
|
<Hint content={<T id={'item.field.cost_account.hint'} />} />
|
||||||
}
|
}
|
||||||
inline={true}
|
inline={true}
|
||||||
intent={inputIntent({ error, touched })}
|
fastField={true}
|
||||||
helperText={<ErrorMessage name="cost_account_id" />}
|
|
||||||
className={classNames(
|
|
||||||
'form-group--cost-account',
|
|
||||||
'form-group--select-list',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
name={'cost_account_id'}
|
||||||
onAccountSelected={(account) => {
|
items={accounts}
|
||||||
form.setFieldValue('cost_account_id', account.id);
|
placeholder={<T id={'select_account'} />}
|
||||||
}}
|
|
||||||
defaultSelectText={<T id={'select_account'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
disabled={!form.values.purchasable}
|
|
||||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
|
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
allowCreate={true}
|
allowCreate={true}
|
||||||
|
fastField={true}
|
||||||
|
disabled={!values.purchasable}
|
||||||
|
purchasable={values.purchasable}
|
||||||
|
shouldUpdate={costAccountFieldShouldUpdate}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
<FastField
|
<FastField
|
||||||
name={'purchase_description'}
|
name={'purchase_description'}
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FastField, ErrorMessage } from 'formik';
|
|
||||||
import { FormGroup } from '@blueprintjs/core';
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
|
||||||
import {
|
import {
|
||||||
AccountsSelectList,
|
AccountsSelect,
|
||||||
|
FFormGroup,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
Col,
|
Col,
|
||||||
Row,
|
Row,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import classNames from 'classnames';
|
|
||||||
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
|
|
||||||
|
|
||||||
|
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
|
||||||
import { accountsFieldShouldUpdate } from './utils';
|
import { accountsFieldShouldUpdate } from './utils';
|
||||||
import { compose, inputIntent } from '@/utils';
|
|
||||||
import { ACCOUNT_TYPE } from '@/constants/accountTypes';
|
import { ACCOUNT_TYPE } from '@/constants/accountTypes';
|
||||||
import { useItemFormContext } from './ItemFormProvider';
|
import { useItemFormContext } from './ItemFormProvider';
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item form inventory sections.
|
* Item form inventory sections.
|
||||||
@@ -31,36 +28,24 @@ function ItemFormInventorySection({ organization: { base_currency } }) {
|
|||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={6}>
|
<Col xs={6}>
|
||||||
{/*------------- Inventory account ------------- */}
|
{/*------------- Inventory Account ------------- */}
|
||||||
<FastField
|
<FFormGroup
|
||||||
name={'inventory_account_id'}
|
|
||||||
accounts={accounts}
|
|
||||||
shouldUpdate={accountsFieldShouldUpdate}
|
|
||||||
>
|
|
||||||
{({ form, field: { value }, meta: { touched, error } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'inventory_account'} />}
|
label={<T id={'inventory_account'} />}
|
||||||
|
name={'inventory_account_id'}
|
||||||
|
items={accounts}
|
||||||
|
fastField={true}
|
||||||
|
shouldUpdate={accountsFieldShouldUpdate}
|
||||||
inline={true}
|
inline={true}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name="inventory_account_id" />}
|
|
||||||
className={classNames(
|
|
||||||
'form-group--item-inventory_account',
|
|
||||||
'form-group--select-list',
|
|
||||||
CLASSES.FILL,
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
name={'inventory_account_id'}
|
||||||
onAccountSelected={(account) => {
|
items={accounts}
|
||||||
form.setFieldValue('inventory_account_id', account.id);
|
placeholder={<T id={'select_account'} />}
|
||||||
}}
|
|
||||||
defaultSelectText={<T id={'select_account'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
filterByTypes={[ACCOUNT_TYPE.INVENTORY]}
|
filterByTypes={[ACCOUNT_TYPE.INVENTORY]}
|
||||||
|
fastField={true}
|
||||||
|
shouldUpdate={accountsFieldShouldUpdate}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export const handleDeleteErrors = (errors) => {
|
|||||||
*/
|
*/
|
||||||
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
|
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
|
||||||
return (
|
return (
|
||||||
newProps.accounts !== oldProps.accounts ||
|
newProps.items !== oldProps.items ||
|
||||||
defaultFastFieldShouldUpdate(newProps, oldProps)
|
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -149,7 +149,7 @@ export const sellPriceFieldShouldUpdate = (newProps, oldProps) => {
|
|||||||
*/
|
*/
|
||||||
export const sellAccountFieldShouldUpdate = (newProps, oldProps) => {
|
export const sellAccountFieldShouldUpdate = (newProps, oldProps) => {
|
||||||
return (
|
return (
|
||||||
newProps.accounts !== oldProps.accounts ||
|
newProps.items !== oldProps.items ||
|
||||||
newProps.sellable !== oldProps.sellable ||
|
newProps.sellable !== oldProps.sellable ||
|
||||||
defaultFastFieldShouldUpdate(newProps, oldProps)
|
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,12 +2,18 @@
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const Schema = Yup.object().shape({
|
const Schema = Yup.object().shape({
|
||||||
accounting_basis: Yup.string().required(),
|
organization: Yup.object({
|
||||||
account_code_required: Yup.boolean().nullable(),
|
accountingBasis: Yup.string().required(),
|
||||||
account_code_unique: Yup.boolean().nullable(),
|
}),
|
||||||
withdrawal_account: Yup.number().nullable(),
|
accounts: Yup.object({
|
||||||
preferred_deposit_account: Yup.number().nullable(),
|
accountCodeRequired: Yup.boolean().nullable(),
|
||||||
preferred_advance_deposit: Yup.number().nullable(),
|
accountCodeUnique: Yup.boolean().nullable(),
|
||||||
|
}),
|
||||||
|
paymentReceives: Yup.object({
|
||||||
|
preferredDepositAccount: Yup.number().nullable(),
|
||||||
|
preferredAdvanceDeposit: Yup.number().nullable(),
|
||||||
|
withdrawalAccount: Yup.number().nullable(),
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AccountantSchema = Schema;
|
export const AccountantSchema = Schema;
|
||||||
|
|||||||
@@ -1,26 +1,21 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { Form, FastField, useFormikContext } from 'formik';
|
import { Form, useFormikContext } from 'formik';
|
||||||
import {
|
import styled from 'styled-components';
|
||||||
FormGroup,
|
import { FormGroup, Radio, Button, Intent } from '@blueprintjs/core';
|
||||||
RadioGroup,
|
|
||||||
Radio,
|
|
||||||
Checkbox,
|
|
||||||
Button,
|
|
||||||
Intent,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
AccountsSelectList,
|
AccountsSelect,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
CardFooterActions,
|
CardFooterActions,
|
||||||
|
FFormGroup,
|
||||||
|
FCheckbox,
|
||||||
|
FRadioGroup,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { handleStringChange, inputIntent } from '@/utils';
|
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from '@/constants/accountTypes';
|
||||||
import { ACCOUNT_TYPE } from '@/constants/accountTypes';
|
|
||||||
|
|
||||||
import { useAccountantFormContext } from './AccountantFormProvider';
|
import { useAccountantFormContext } from './AccountantFormProvider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,15 +23,13 @@ import { useAccountantFormContext } from './AccountantFormProvider';
|
|||||||
*/
|
*/
|
||||||
export default function AccountantForm() {
|
export default function AccountantForm() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const { accounts } = useAccountantFormContext();
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
const handleCloseClick = () => {
|
const handleCloseClick = () => {
|
||||||
history.go(-1);
|
history.go(-1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { accounts } = useAccountantFormContext();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
{/* ----------- Accounts ----------- */}
|
{/* ----------- Accounts ----------- */}
|
||||||
@@ -48,81 +41,54 @@ export default function AccountantForm() {
|
|||||||
}
|
}
|
||||||
className={'accounts-checkbox'}
|
className={'accounts-checkbox'}
|
||||||
>
|
>
|
||||||
{/*------------ account code required -----------*/}
|
{/*------------ Account Code (required) -----------*/}
|
||||||
<FastField name={'account_code_required'} type={'checkbox'}>
|
<FFormGroup inline={true} name={'accounts.accountCodeRequired'}>
|
||||||
{({ field }) => (
|
<FCheckbox
|
||||||
<FormGroup inline={true}>
|
inline={true}
|
||||||
<Checkbox
|
label={
|
||||||
|
<T id={'make_account_code_required_when_create_a_new_accounts'} />
|
||||||
|
}
|
||||||
|
name={'accounts.accountCodeRequired'}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*------------ Account Code (unique) -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'accounts.accountCodeUnique'}
|
||||||
|
type={'checkbox'}
|
||||||
|
inline={true}
|
||||||
|
>
|
||||||
|
<FCheckbox
|
||||||
inline={true}
|
inline={true}
|
||||||
label={
|
label={
|
||||||
<T
|
<T
|
||||||
id={'make_account_code_required_when_create_a_new_accounts'}
|
id={'should_account_code_be_unique_when_create_a_new_account'}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
name={'account_code_required'}
|
name={'accounts.accountCodeUnique'}
|
||||||
{...field}
|
|
||||||
/>
|
/>
|
||||||
|
</FFormGroup>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
{/* ----------- Accounting Basis ----------- */}
|
||||||
{/*------------ 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
|
<FormGroup
|
||||||
|
name={'organization.accountingBasis'}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
label={
|
label={
|
||||||
<strong>
|
<strong>
|
||||||
<T id={'accounting_basis_'} />
|
<T id={'accounting_basis_'} />
|
||||||
</strong>
|
</strong>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<RadioGroup
|
<FRadioGroup name={'organization.accountingBasis'} inline={true}>
|
||||||
inline={true}
|
|
||||||
selectedValue={value}
|
|
||||||
onChange={handleStringChange((_value) => {
|
|
||||||
setFieldValue('accounting_basis', _value);
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Radio label={intl.get('cash')} value="cash" />
|
<Radio label={intl.get('cash')} value="cash" />
|
||||||
<Radio label={intl.get('accrual')} value="accrual" />
|
<Radio label={intl.get('accrual')} value="accrual" />
|
||||||
</RadioGroup>
|
</FRadioGroup>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ----------- Deposit customer account ----------- */}
|
{/* ----------- Deposit Customer Account ----------- */}
|
||||||
<FastField name={'preferred_deposit_account'}>
|
<AccountantFormGroup
|
||||||
{({
|
name={'paymentReceives.preferredDepositAccount'}
|
||||||
form: { values, setFieldValue },
|
|
||||||
field: { value },
|
|
||||||
meta: { error, touched },
|
|
||||||
}) => (
|
|
||||||
<FormGroup
|
|
||||||
label={
|
label={
|
||||||
<strong>
|
<strong>
|
||||||
<T id={'deposit_customer_account'} />
|
<T id={'deposit_customer_account'} />
|
||||||
@@ -136,33 +102,24 @@ export default function AccountantForm() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={inputIntent({ error, touched })}
|
fastField={true}
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
name={'paymentReceives.preferredDepositAccount'}
|
||||||
onAccountSelected={({ id }) => {
|
items={accounts}
|
||||||
setFieldValue('preferred_deposit_account', id);
|
placeholder={<T id={'select_payment_account'} />}
|
||||||
}}
|
|
||||||
selectedAccountId={value}
|
|
||||||
defaultSelectText={<T id={'select_payment_account'} />}
|
|
||||||
filterByTypes={[
|
filterByTypes={[
|
||||||
ACCOUNT_TYPE.CASH,
|
ACCOUNT_TYPE.CASH,
|
||||||
ACCOUNT_TYPE.BANK,
|
ACCOUNT_TYPE.BANK,
|
||||||
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||||
]}
|
]}
|
||||||
|
fastField={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</AccountantFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ----------- Withdrawal vendor account ----------- */}
|
{/* ----------- Withdrawal Vendor Account ----------- */}
|
||||||
<FastField name={'withdrawal_account'}>
|
<AccountantFormGroup
|
||||||
{({
|
name={'billPayments.withdrawalAccount'}
|
||||||
form: { values, setFieldValue },
|
|
||||||
field: { value },
|
|
||||||
meta: { error, touched },
|
|
||||||
}) => (
|
|
||||||
<FormGroup
|
|
||||||
label={
|
label={
|
||||||
<strong>
|
<strong>
|
||||||
<T id={'withdrawal_vendor_account'} />
|
<T id={'withdrawal_vendor_account'} />
|
||||||
@@ -176,33 +133,24 @@ export default function AccountantForm() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={inputIntent({ error, touched })}
|
fastField={true}
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
name={'billPayments.withdrawalAccount'}
|
||||||
onAccountSelected={({ id }) => {
|
items={accounts}
|
||||||
setFieldValue('withdrawal_account', id);
|
placeholder={<T id={'select_payment_account'} />}
|
||||||
}}
|
|
||||||
selectedAccountId={value}
|
|
||||||
defaultSelectText={<T id={'select_payment_account'} />}
|
|
||||||
filterByTypes={[
|
filterByTypes={[
|
||||||
ACCOUNT_TYPE.CASH,
|
ACCOUNT_TYPE.CASH,
|
||||||
ACCOUNT_TYPE.BANK,
|
ACCOUNT_TYPE.BANK,
|
||||||
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||||
]}
|
]}
|
||||||
|
fastField={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</AccountantFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ----------- Withdrawal customer account ----------- */}
|
{/* ----------- Withdrawal Customer Account ----------- */}
|
||||||
<FastField name={'preferred_advance_deposit'}>
|
<AccountantFormGroup
|
||||||
{({
|
name={'paymentReceives.preferredAdvanceDeposit'}
|
||||||
form: { values, setFieldValue },
|
|
||||||
field: { value },
|
|
||||||
meta: { error, touched },
|
|
||||||
}) => (
|
|
||||||
<FormGroup
|
|
||||||
label={
|
label={
|
||||||
<strong>
|
<strong>
|
||||||
<T id={'customer_advance_deposit'} />
|
<T id={'customer_advance_deposit'} />
|
||||||
@@ -216,20 +164,16 @@ export default function AccountantForm() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={inputIntent({ error, touched })}
|
fastField={true}
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
name={'paymentReceives.preferredAdvanceDeposit'}
|
||||||
onAccountSelected={({ id }) => {
|
items={accounts}
|
||||||
setFieldValue('preferred_advance_deposit', id);
|
placeholder={<T id={'select_payment_account'} />}
|
||||||
}}
|
filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
|
||||||
selectedAccountId={value}
|
fastField={true}
|
||||||
defaultSelectText={<T id={'select_payment_account'} />}
|
|
||||||
// filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
|
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</AccountantFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
<CardFooterActions>
|
<CardFooterActions>
|
||||||
<Button intent={Intent.PRIMARY} loading={isSubmitting} type="submit">
|
<Button intent={Intent.PRIMARY} loading={isSubmitting} type="submit">
|
||||||
@@ -242,3 +186,7 @@ export default function AccountantForm() {
|
|||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AccountantFormGroup = styled(FFormGroup)`
|
||||||
|
width: 450px;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,55 +1,66 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import * as R from 'ramda';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import { pick } from 'lodash';
|
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { flatten, unflatten } from 'flat';
|
||||||
|
|
||||||
import { AppToaster } from '@/components';
|
import { AppToaster } from '@/components';
|
||||||
|
|
||||||
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
|
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
|
||||||
import withSettings from '@/containers/Settings/withSettings';
|
import withSettings from '@/containers/Settings/withSettings';
|
||||||
|
|
||||||
import AccountantForm from './AccountantForm';
|
import AccountantForm from './AccountantForm';
|
||||||
import { AccountantSchema } from './Accountant.schema';
|
import { AccountantSchema } from './Accountant.schema';
|
||||||
import { useAccountantFormContext } from './AccountantFormProvider';
|
import { useAccountantFormContext } from './AccountantFormProvider';
|
||||||
import { transformToOptions } from './utils';
|
import { transferObjectOptionsToArray } from './utils';
|
||||||
import { compose, transformGeneralSettings } from '@/utils';
|
import { compose, transformToForm, transfromToSnakeCase } from '@/utils';
|
||||||
|
|
||||||
import '@/style/pages/Preferences/Accounting.scss';
|
import '@/style/pages/Preferences/Accounting.scss';
|
||||||
|
|
||||||
|
const defaultFormValues = flatten({
|
||||||
|
organization: {
|
||||||
|
accountingBasis: 'accrual',
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
accountCodeRequired: false,
|
||||||
|
accountCodeUnique: false,
|
||||||
|
},
|
||||||
|
billPayments: {
|
||||||
|
withdrawalAccount: '',
|
||||||
|
},
|
||||||
|
paymentReceives: {
|
||||||
|
preferredDepositAccount: '',
|
||||||
|
preferredAdvanceDeposit: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Accountant preferences.
|
// Accountant preferences.
|
||||||
function AccountantFormPage({
|
function AccountantFormPage({
|
||||||
//# withDashboardActions
|
//# withDashboardActions
|
||||||
changePreferencesPageTitle,
|
changePreferencesPageTitle,
|
||||||
|
|
||||||
// #withSettings
|
// #withSettings
|
||||||
organizationSettings,
|
allSettings,
|
||||||
paymentReceiveSettings,
|
|
||||||
accountsSettings,
|
|
||||||
billPaymentSettings,
|
|
||||||
}) {
|
}) {
|
||||||
const { saveSettingMutate } = useAccountantFormContext();
|
const { saveSettingMutate } = useAccountantFormContext();
|
||||||
|
|
||||||
const accountantSettings = {
|
|
||||||
...billPaymentSettings,
|
|
||||||
...accountsSettings,
|
|
||||||
...pick(organizationSettings, ['accountingBasis']),
|
|
||||||
...pick(paymentReceiveSettings, ['preferredDepositAccount', 'preferredAdvanceDeposit']),
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialValues = {
|
|
||||||
...transformGeneralSettings(accountantSettings),
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePreferencesPageTitle(intl.get('accountant'));
|
changePreferencesPageTitle(intl.get('accountant'));
|
||||||
}, [changePreferencesPageTitle]);
|
}, [changePreferencesPageTitle]);
|
||||||
|
|
||||||
|
const initialValues = unflatten({
|
||||||
|
...defaultFormValues,
|
||||||
|
...transformToForm(flatten(allSettings), defaultFormValues),
|
||||||
|
});
|
||||||
|
// Handle the form submitting.
|
||||||
const handleFormSubmit = (values, { setSubmitting }) => {
|
const handleFormSubmit = (values, { setSubmitting }) => {
|
||||||
const options = transformToOptions(values);
|
const options = R.compose(
|
||||||
|
transferObjectOptionsToArray,
|
||||||
|
transfromToSnakeCase,
|
||||||
|
)(values);
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
const onSuccess = () => {
|
const onSuccess = () => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get('the_accountant_preferences_has_been_saved'),
|
message: intl.get('the_accountant_preferences_has_been_saved'),
|
||||||
@@ -57,8 +68,7 @@ function AccountantFormPage({
|
|||||||
});
|
});
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
|
const onError = () => {
|
||||||
const onError = (errors) => {
|
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
saveSettingMutate({ options }).then(onSuccess).catch(onError);
|
saveSettingMutate({ options }).then(onSuccess).catch(onError);
|
||||||
@@ -75,18 +85,8 @@ function AccountantFormPage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withSettings(
|
withSettings(({ allSettings }) => ({
|
||||||
({
|
allSettings,
|
||||||
organizationSettings,
|
})),
|
||||||
paymentReceiveSettings,
|
|
||||||
accountsSettings,
|
|
||||||
billPaymentSettings,
|
|
||||||
}) => ({
|
|
||||||
organizationSettings,
|
|
||||||
paymentReceiveSettings,
|
|
||||||
accountsSettings,
|
|
||||||
billPaymentSettings,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
)(AccountantFormPage);
|
)(AccountantFormPage);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function AccountantFormProvider({ ...props }) {
|
|||||||
// Fetches the accounts list.
|
// Fetches the accounts list.
|
||||||
const { isLoading: isAccountsLoading, data: accounts } = useAccounts();
|
const { isLoading: isAccountsLoading, data: accounts } = useAccounts();
|
||||||
|
|
||||||
//Fetches Organization Settings.
|
// Fetches Organization Settings.
|
||||||
const { isLoading: isSettingsLoading } = useSettings();
|
const { isLoading: isSettingsLoading } = useSettings();
|
||||||
|
|
||||||
// Save Organization Settings.
|
// Save Organization Settings.
|
||||||
@@ -29,7 +29,7 @@ function AccountantFormProvider({ ...props }) {
|
|||||||
isAccountsLoading,
|
isAccountsLoading,
|
||||||
saveSettingMutate,
|
saveSettingMutate,
|
||||||
};
|
};
|
||||||
|
// Detarmines whether if any query is loading.
|
||||||
const isLoading = isSettingsLoading || isAccountsLoading;
|
const isLoading = isSettingsLoading || isAccountsLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,38 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
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: 'preferred_deposit_account',
|
|
||||||
value: option.preferred_deposit_account,
|
|
||||||
group: 'payment_receives',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'preferred_advance_deposit',
|
|
||||||
value: option.preferred_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',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
export const transferObjectOptionsToArray = (input) =>
|
||||||
|
Object.entries(input).flatMap(([group, options]) =>
|
||||||
|
Object.entries(options).map(([key, value]) => ({ group, key, value })),
|
||||||
|
);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ActionMenuList, useCurrenciesTableColumns } from './components';
|
|||||||
|
|
||||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Currencies table.
|
* Currencies table.
|
||||||
@@ -46,7 +47,7 @@ function CurrenciesDataTable({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<CurrencieDataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={currencies}
|
data={currencies}
|
||||||
loading={isCurrenciesLoading}
|
loading={isCurrenciesLoading}
|
||||||
@@ -68,3 +69,11 @@ export default compose(
|
|||||||
withDialogActions,
|
withDialogActions,
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
)(CurrenciesDataTable);
|
)(CurrenciesDataTable);
|
||||||
|
|
||||||
|
const CurrencieDataTable = styled(DataTable)`
|
||||||
|
.table .th,
|
||||||
|
.table .td {
|
||||||
|
padding-top: 0.4rem;
|
||||||
|
padding-bottom: 0.4rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ import React from 'react';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Form } from 'formik';
|
import { Form } from 'formik';
|
||||||
import { Button, FormGroup, InputGroup, Intent } from '@blueprintjs/core';
|
import { Button, FormGroup, Intent } from '@blueprintjs/core';
|
||||||
import { TimezonePicker } from '@blueprintjs/timezone';
|
import { TimezonePicker } from '@blueprintjs/timezone';
|
||||||
import { ErrorMessage, FastField } from 'formik';
|
import { ErrorMessage, FastField } from 'formik';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ListSelect,
|
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
|
FFormGroup,
|
||||||
|
FInputGroup,
|
||||||
|
FSelect,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { inputIntent } from '@/utils';
|
import { inputIntent } from '@/utils';
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
@@ -46,153 +48,114 @@ export default function PreferencesGeneralForm({ isSubmitting }) {
|
|||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
{/* ---------- Organization name ---------- */}
|
{/* ---------- Organization name ---------- */}
|
||||||
<FastField name={'name'}>
|
<FFormGroup
|
||||||
{({ field, meta: { error, touched } }) => (
|
name={'name'}
|
||||||
<FormGroup
|
|
||||||
label={<T id={'organization_name'} />}
|
label={<T id={'organization_name'} />}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
inline={true}
|
inline={true}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
className={'form-group--org-name'}
|
|
||||||
helperText={<T id={'shown_on_sales_forms_and_purchase_orders'} />}
|
helperText={<T id={'shown_on_sales_forms_and_purchase_orders'} />}
|
||||||
|
fastField={true}
|
||||||
>
|
>
|
||||||
<InputGroup medium={'true'} {...field} />
|
<FInputGroup medium={'true'} name={'name'} fastField={true} />
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ---------- Industry ---------- */}
|
{/* ---------- Industry ---------- */}
|
||||||
<FastField name={'industry'}>
|
<FFormGroup
|
||||||
{({ field, meta: { error, touched } }) => (
|
name={'industry'}
|
||||||
<FormGroup
|
|
||||||
label={<T id={'organization_industry'} />}
|
label={<T id={'organization_industry'} />}
|
||||||
inline={true}
|
inline={true}
|
||||||
intent={inputIntent({ error, touched })}
|
fastField={true}
|
||||||
helperText={<ErrorMessage name="industry" />}
|
|
||||||
className={'form-group--org-industry'}
|
|
||||||
>
|
>
|
||||||
<InputGroup medium={'true'} {...field} />
|
<FInputGroup name={'industry'} medium={'true'} fastField={true} />
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ---------- Location ---------- */}
|
{/* ---------- Location ---------- */}
|
||||||
<FastField name={'location'}>
|
<FFormGroup
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
name={'location'}
|
||||||
<FormGroup
|
|
||||||
label={<T id={'business_location'} />}
|
label={<T id={'business_location'} />}
|
||||||
className={classNames(
|
|
||||||
'form-group--business-location',
|
|
||||||
CLASSES.FILL,
|
|
||||||
)}
|
|
||||||
inline={true}
|
inline={true}
|
||||||
helperText={<ErrorMessage name="location" />}
|
fastField={true}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
>
|
>
|
||||||
<ListSelect
|
<FSelect
|
||||||
|
name={'location'}
|
||||||
items={Countries}
|
items={Countries}
|
||||||
onItemSelect={({ value }) => {
|
valueAccessor={'countryCode'}
|
||||||
form.setFieldValue('location', value);
|
labelAccessor={'countryCode'}
|
||||||
}}
|
textAccessor={'name'}
|
||||||
selectedItem={value}
|
placeholder={<T id={'select_business_location'} />}
|
||||||
selectedItemProp={'value'}
|
|
||||||
defaultText={<T id={'select_business_location'} />}
|
|
||||||
textProp={'name'}
|
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
|
fastField={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ---------- Base currency ---------- */}
|
{/* ---------- Base currency ---------- */}
|
||||||
<FastField
|
<FFormGroup
|
||||||
name={'base_currency'}
|
name={'base_currency'}
|
||||||
baseCurrencyDisabled={baseCurrencyDisabled}
|
baseCurrencyDisabled={baseCurrencyDisabled}
|
||||||
shouldUpdate={shouldBaseCurrencyUpdate}
|
|
||||||
>
|
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'base_currency'} />}
|
label={<T id={'base_currency'} />}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
className={classNames('form-group--base-currency', CLASSES.FILL)}
|
|
||||||
inline={true}
|
inline={true}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={
|
helperText={
|
||||||
<T
|
<T
|
||||||
id={
|
id={'you_can_t_change_the_base_currency_as_there_are_transactions'}
|
||||||
'you_can_t_change_the_base_currency_as_there_are_transactions'
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
fastField={true}
|
||||||
|
shouldUpdate={shouldBaseCurrencyUpdate}
|
||||||
>
|
>
|
||||||
<ListSelect
|
<FSelect
|
||||||
|
name={'base_currency'}
|
||||||
items={Currencies}
|
items={Currencies}
|
||||||
onItemSelect={(currency) => {
|
valueAccessor={'key'}
|
||||||
form.setFieldValue('base_currency', currency.key);
|
textAccessor={'name'}
|
||||||
}}
|
labelAccessor={'key'}
|
||||||
selectedItem={value}
|
placeholder={<T id={'select_base_currency'} />}
|
||||||
selectedItemProp={'key'}
|
|
||||||
defaultText={<T id={'select_base_currency'} />}
|
|
||||||
textProp={'name'}
|
|
||||||
labelProp={'key'}
|
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
disabled={baseCurrencyDisabled}
|
disabled={baseCurrencyDisabled}
|
||||||
|
fastField={true}
|
||||||
|
shouldUpdate={shouldBaseCurrencyUpdate}
|
||||||
|
baseCurrencyDisabled={baseCurrencyDisabled}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* --------- Fiscal Year ----------- */}
|
{/* --------- Fiscal Year ----------- */}
|
||||||
<FastField name={'fiscal_year'}>
|
<FFormGroup
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
name={'fiscal_year'}
|
||||||
<FormGroup
|
|
||||||
label={<T id={'fiscal_year'} />}
|
label={<T id={'fiscal_year'} />}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
className={classNames('form-group--fiscal-year', CLASSES.FILL)}
|
|
||||||
inline={true}
|
inline={true}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<T id={'for_reporting_you_can_specify_any_month'} />}
|
helperText={<T id={'for_reporting_you_can_specify_any_month'} />}
|
||||||
|
fastField={true}
|
||||||
>
|
>
|
||||||
<ListSelect
|
<FSelect
|
||||||
|
name={'fiscal_year'}
|
||||||
items={FiscalYear}
|
items={FiscalYear}
|
||||||
onItemSelect={(option) => {
|
valueAccessor={'key'}
|
||||||
form.setFieldValue('fiscal_year', option.key);
|
textAccessor={'name'}
|
||||||
}}
|
placeholder={<T id={'select_fiscal_year'} />}
|
||||||
selectedItem={value}
|
|
||||||
selectedItemProp={'key'}
|
|
||||||
defaultText={<T id={'select_fiscal_year'} />}
|
|
||||||
textProp={'name'}
|
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
|
fastField={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ---------- Language ---------- */}
|
{/* ---------- Language ---------- */}
|
||||||
<FastField name={'language'}>
|
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
<FormGroup
|
||||||
|
name={'language'}
|
||||||
label={<T id={'language'} />}
|
label={<T id={'language'} />}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
inline={true}
|
inline={true}
|
||||||
className={classNames('form-group--language', CLASSES.FILL)}
|
fastField={true}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name="language" />}
|
|
||||||
>
|
>
|
||||||
<ListSelect
|
<FSelect
|
||||||
|
name={'language'}
|
||||||
items={Languages}
|
items={Languages}
|
||||||
selectedItemProp={'value'}
|
valueAccessor={'value'}
|
||||||
textProp={'name'}
|
textAccessor={'name'}
|
||||||
defaultText={<T id={'select_language'} />}
|
placeholder={<T id={'select_language'} />}
|
||||||
selectedItem={value}
|
|
||||||
onItemSelect={(item) =>
|
|
||||||
form.setFieldValue('language', item.value)
|
|
||||||
}
|
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
|
fastField={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ---------- Time zone ---------- */}
|
{/* ---------- Time zone ---------- */}
|
||||||
<FastField name={'timezone'}>
|
<FastField name={'timezone'}>
|
||||||
@@ -222,30 +185,24 @@ export default function PreferencesGeneralForm({ isSubmitting }) {
|
|||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
{/* --------- Data format ----------- */}
|
{/* --------- Data format ----------- */}
|
||||||
<FastField name={'date_format'}>
|
<FFormGroup
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
name={'date_format'}
|
||||||
<FormGroup
|
|
||||||
label={<T id={'date_format'} />}
|
label={<T id={'date_format'} />}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
inline={true}
|
inline={true}
|
||||||
className={classNames('form-group--date-format', CLASSES.FILL)}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name="date_format" />}
|
helperText={<ErrorMessage name="date_format" />}
|
||||||
|
fastField={true}
|
||||||
>
|
>
|
||||||
<ListSelect
|
<FSelect
|
||||||
|
name={'date_format'}
|
||||||
items={dateFormats}
|
items={dateFormats}
|
||||||
onItemSelect={(dateFormat) => {
|
valueAccessor={'key'}
|
||||||
form.setFieldValue('date_format', dateFormat.key);
|
textAccessor={'label'}
|
||||||
}}
|
placeholder={<T id={'select_date_format'} />}
|
||||||
selectedItem={value}
|
|
||||||
selectedItemProp={'key'}
|
|
||||||
defaultText={<T id={'select_date_format'} />}
|
|
||||||
textProp={'label'}
|
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
|
fastField={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
<CardFooterActions>
|
<CardFooterActions>
|
||||||
<Button loading={isSubmitting} intent={Intent.PRIMARY} type="submit">
|
<Button loading={isSubmitting} intent={Intent.PRIMARY} type="submit">
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ function GeneralFormPage({
|
|||||||
|
|
||||||
// Initial values.
|
// Initial values.
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
|
...defaultValues,
|
||||||
...transformToForm(organization.metadata, defaultValues),
|
...transformToForm(organization.metadata, defaultValues),
|
||||||
};
|
};
|
||||||
// Handle the form submit.
|
// Handle the form submit.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ function GeneralFormProvider({ ...props }) {
|
|||||||
const { isLoading: isOrganizationLoading, data: organization } =
|
const { isLoading: isOrganizationLoading, data: organization } =
|
||||||
useCurrentOrganization();
|
useCurrentOrganization();
|
||||||
|
|
||||||
|
// Fetch date format options.
|
||||||
const { data: dateFormats, isLoading: isDateFormatsLoading } =
|
const { data: dateFormats, isLoading: isDateFormatsLoading } =
|
||||||
useDateFormats();
|
useDateFormats();
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Form, FastField, useFormikContext } from 'formik';
|
import { Form, useFormikContext } from 'formik';
|
||||||
import { FormGroup, Button, Intent } from '@blueprintjs/core';
|
import { Button, Intent } from '@blueprintjs/core';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
import {
|
import {
|
||||||
AccountsSelectList,
|
AccountsSelect,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
CardFooterActions
|
FFormGroup,
|
||||||
|
CardFooterActions,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { inputIntent } from '@/utils';
|
|
||||||
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from '@/constants/accountTypes';
|
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from '@/constants/accountTypes';
|
||||||
|
|
||||||
import { useItemPreferencesFormContext } from './ItemPreferencesFormProvider';
|
import { useItemPreferencesFormContext } from './ItemPreferencesFormProvider';
|
||||||
@@ -29,14 +30,9 @@ export default function ItemForm() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
{/* ----------- preferred sell account ----------- */}
|
{/* ----------- Preferred Sell Account ----------- */}
|
||||||
<FastField name={'preferred_sell_account'}>
|
<ItemFormGroup
|
||||||
{({
|
name={'preferred_sell_account'}
|
||||||
form: { values, setFieldValue },
|
|
||||||
field: { value },
|
|
||||||
meta: { error, touched },
|
|
||||||
}) => (
|
|
||||||
<FormGroup
|
|
||||||
label={
|
label={
|
||||||
<strong>
|
<strong>
|
||||||
<T id={'preferred_sell_account'} />
|
<T id={'preferred_sell_account'} />
|
||||||
@@ -50,29 +46,19 @@ export default function ItemForm() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={inputIntent({ error, touched })}
|
fastField={true}
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
name={'preferred_sell_account'}
|
||||||
onAccountSelected={({ id }) => {
|
items={accounts}
|
||||||
setFieldValue('preferred_sell_account', id);
|
placeholder={<T id={'select_payment_account'} />}
|
||||||
}}
|
|
||||||
selectedAccountId={value}
|
|
||||||
defaultSelectText={<T id={'select_payment_account'} />}
|
|
||||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
|
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</ItemFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ----------- preferred cost account ----------- */}
|
{/* ----------- Preferred Cost Account ----------- */}
|
||||||
<FastField name={'preferred_cost_account'}>
|
<ItemFormGroup
|
||||||
{({
|
name={'preferred_cost_account'}
|
||||||
form: { values, setFieldValue },
|
|
||||||
field: { value },
|
|
||||||
meta: { error, touched },
|
|
||||||
}) => (
|
|
||||||
<FormGroup
|
|
||||||
label={
|
label={
|
||||||
<strong>
|
<strong>
|
||||||
<T id={'preferred_cost_account'} />
|
<T id={'preferred_cost_account'} />
|
||||||
@@ -86,29 +72,19 @@ export default function ItemForm() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={inputIntent({ error, touched })}
|
fastField={true}
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
name={'preferred_cost_account'}
|
||||||
onAccountSelected={({ id }) => {
|
items={accounts}
|
||||||
setFieldValue('preferred_cost_account', id);
|
placeholder={<T id={'select_payment_account'} />}
|
||||||
}}
|
|
||||||
selectedAccountId={value}
|
|
||||||
defaultSelectText={<T id={'select_payment_account'} />}
|
|
||||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
|
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</ItemFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ----------- preferred inventory account ----------- */}
|
{/* ----------- Preferred Inventory Account ----------- */}
|
||||||
<FastField name={'preferred_inventory_account'}>
|
<ItemFormGroup
|
||||||
{({
|
name={'preferred_inventory_account'}
|
||||||
form: { values, setFieldValue },
|
|
||||||
field: { value },
|
|
||||||
meta: { error, touched },
|
|
||||||
}) => (
|
|
||||||
<FormGroup
|
|
||||||
label={
|
label={
|
||||||
<strong>
|
<strong>
|
||||||
<T id={'preferred_inventory_account'} />
|
<T id={'preferred_inventory_account'} />
|
||||||
@@ -122,20 +98,15 @@ export default function ItemForm() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={inputIntent({ error, touched })}
|
fastField={true}
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
name={'preferred_inventory_account'}
|
||||||
onAccountSelected={({ id }) => {
|
items={accounts}
|
||||||
setFieldValue('preferred_inventory_account', id);
|
placeholder={<T id={'select_payment_account'} />}
|
||||||
}}
|
|
||||||
selectedAccountId={value}
|
|
||||||
defaultSelectText={<T id={'select_payment_account'} />}
|
|
||||||
filterByTypes={[ACCOUNT_TYPE.INVENTORY]}
|
filterByTypes={[ACCOUNT_TYPE.INVENTORY]}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</ItemFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
<CardFooterActions>
|
<CardFooterActions>
|
||||||
<Button intent={Intent.PRIMARY} loading={isSubmitting} type="submit">
|
<Button intent={Intent.PRIMARY} loading={isSubmitting} type="submit">
|
||||||
@@ -148,3 +119,7 @@ export default function ItemForm() {
|
|||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ItemFormGroup = styled(FFormGroup)`
|
||||||
|
max-width: 400px;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -11,10 +11,21 @@ import ItemPreferencesForm from './ItemPreferencesForm';
|
|||||||
import { useItemPreferencesFormContext } from './ItemPreferencesFormProvider';
|
import { useItemPreferencesFormContext } from './ItemPreferencesFormProvider';
|
||||||
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
|
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
|
||||||
import withSettings from '@/containers/Settings/withSettings';
|
import withSettings from '@/containers/Settings/withSettings';
|
||||||
import { compose, optionsMapToArray, transformGeneralSettings } from '@/utils';
|
import {
|
||||||
|
compose,
|
||||||
|
optionsMapToArray,
|
||||||
|
transformGeneralSettings,
|
||||||
|
transformToForm,
|
||||||
|
} from '@/utils';
|
||||||
|
|
||||||
import '@/style/pages/Preferences/Accounting.scss';
|
import '@/style/pages/Preferences/Accounting.scss';
|
||||||
|
|
||||||
|
const defaultFormValues = {
|
||||||
|
preferred_sell_account: '',
|
||||||
|
preferred_cost_account: '',
|
||||||
|
preferred_inventory_account: '',
|
||||||
|
};
|
||||||
|
|
||||||
// item form page preferences.
|
// item form page preferences.
|
||||||
function ItemPreferencesFormPage({
|
function ItemPreferencesFormPage({
|
||||||
// #withSettings
|
// #withSettings
|
||||||
@@ -25,16 +36,13 @@ function ItemPreferencesFormPage({
|
|||||||
}) {
|
}) {
|
||||||
const { saveSettingMutate } = useItemPreferencesFormContext();
|
const { saveSettingMutate } = useItemPreferencesFormContext();
|
||||||
|
|
||||||
const itemPerferencesSettings = {
|
|
||||||
...omit(itemsSettings, ['tableSize']),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initial values.
|
// Initial values.
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
preferred_sell_account: '',
|
...defaultFormValues,
|
||||||
preferred_cost_account: '',
|
...transformToForm(
|
||||||
preferred_inventory_account: '',
|
transformGeneralSettings(itemsSettings),
|
||||||
...transformGeneralSettings(itemPerferencesSettings),
|
defaultFormValues,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { CLASSES } from '@/constants/classes';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
FFormGroup,
|
FFormGroup,
|
||||||
AccountsSelectList,
|
AccountsSelect,
|
||||||
VendorSelectField,
|
VendorSelectField,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
InputPrependText,
|
InputPrependText,
|
||||||
@@ -211,41 +211,30 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) {
|
|||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
{/* ------------ Payment account ------------ */}
|
{/* ------------ Payment account ------------ */}
|
||||||
<FastField
|
<FFormGroup
|
||||||
name={'payment_account_id'}
|
name={'payment_account_id'}
|
||||||
accounts={accounts}
|
|
||||||
shouldUpdate={accountsFieldShouldUpdate}
|
|
||||||
>
|
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'payment_account'} />}
|
label={<T id={'payment_account'} />}
|
||||||
className={classNames(
|
labelInfo={<FieldRequiredHint />}
|
||||||
'form-group--payment_account_id',
|
items={accounts}
|
||||||
'form-group--select-list',
|
shouldUpdate={accountsFieldShouldUpdate}
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
inline={true}
|
inline={true}
|
||||||
labelInfo={<FieldRequiredHint />}
|
fastField={true}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name={'payment_account_id'} />}
|
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
name={'payment_account_id'}
|
||||||
|
items={accounts}
|
||||||
|
placeholder={<T id={'select_payment_account'} />}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
onAccountSelected={(account) => {
|
|
||||||
form.setFieldValue('payment_account_id', account.id);
|
|
||||||
}}
|
|
||||||
defaultSelectText={<T id={'select_payment_account'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
filterByTypes={[
|
filterByTypes={[
|
||||||
ACCOUNT_TYPE.CASH,
|
ACCOUNT_TYPE.CASH,
|
||||||
ACCOUNT_TYPE.BANK,
|
ACCOUNT_TYPE.BANK,
|
||||||
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||||
]}
|
]}
|
||||||
|
shouldUpdate={accountsFieldShouldUpdate}
|
||||||
|
fastField={true}
|
||||||
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ------------ Reference ------------ */}
|
{/* ------------ Reference ------------ */}
|
||||||
<FastField name={'reference'}>
|
<FastField name={'reference'}>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export const vendorsFieldShouldUpdate = (newProps, oldProps) => {
|
|||||||
*/
|
*/
|
||||||
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
|
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
|
||||||
return (
|
return (
|
||||||
newProps.accounts !== oldProps.accounts ||
|
newProps.items !== oldProps.items ||
|
||||||
defaultFastFieldShouldUpdate(newProps, oldProps)
|
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,21 +16,21 @@ function InvoicesListProvider({ query, tableStateChanged, ...props }) {
|
|||||||
const { data: invoicesViews, isLoading: isViewsLoading } =
|
const { data: invoicesViews, isLoading: isViewsLoading } =
|
||||||
useResourceViews('sale_invoices');
|
useResourceViews('sale_invoices');
|
||||||
|
|
||||||
// Fetch the accounts resource fields.
|
// Fetch resource fields of the sale invoices.
|
||||||
const {
|
const {
|
||||||
data: resourceMeta,
|
data: resourceMeta,
|
||||||
isLoading: isResourceLoading,
|
isLoading: isResourceLoading,
|
||||||
isFetching: isResourceFetching,
|
isFetching: isResourceFetching,
|
||||||
} = useResourceMeta('sale_invoices');
|
} = useResourceMeta('sale_invoices');
|
||||||
|
|
||||||
// Fetch accounts list according to the given custom view id.
|
// Fetch sale invoices of the given query.
|
||||||
const {
|
const {
|
||||||
data: { invoices, pagination, filterMeta },
|
data: { invoices, pagination, filterMeta },
|
||||||
isFetching: isInvoicesFetching,
|
isFetching: isInvoicesFetching,
|
||||||
isLoading: isInvoicesLoading,
|
isLoading: isInvoicesLoading,
|
||||||
} = useInvoices(query, { keepPreviousData: true });
|
} = useInvoices(query, { keepPreviousData: true });
|
||||||
|
|
||||||
// Detarmines the datatable empty status.
|
// Detarmines whether the table should show empty state.
|
||||||
const isEmptyStatus =
|
const isEmptyStatus =
|
||||||
isEmpty(invoices) && !tableStateChanged && !isInvoicesLoading;
|
isEmpty(invoices) && !tableStateChanged && !isInvoicesLoading;
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
} from '@/utils';
|
} from '@/utils';
|
||||||
import {
|
import {
|
||||||
FFormGroup,
|
FFormGroup,
|
||||||
AccountsSelectList,
|
AccountsSelect,
|
||||||
CustomerSelectField,
|
CustomerSelectField,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
Icon,
|
Icon,
|
||||||
@@ -285,41 +285,30 @@ function PaymentReceiveHeaderFields({
|
|||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
{/* ------------ Deposit account ------------ */}
|
{/* ------------ Deposit account ------------ */}
|
||||||
<FastField
|
<FFormGroup
|
||||||
name={'deposit_account_id'}
|
name={'deposit_account_id'}
|
||||||
accounts={accounts}
|
|
||||||
shouldUpdate={accountsFieldShouldUpdate}
|
|
||||||
>
|
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'deposit_to'} />}
|
label={<T id={'deposit_to'} />}
|
||||||
className={classNames(
|
|
||||||
'form-group--deposit_account_id',
|
|
||||||
'form-group--select-list',
|
|
||||||
CLASSES.FILL,
|
|
||||||
)}
|
|
||||||
inline={true}
|
inline={true}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={inputIntent({ error, touched })}
|
items={accounts}
|
||||||
helperText={<ErrorMessage name={'deposit_account_id'} />}
|
shouldUpdate={accountsFieldShouldUpdate}
|
||||||
|
fastField={true}
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
name={'deposit_account_id'}
|
||||||
|
items={accounts}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
onAccountSelected={(account) => {
|
placeholder={<T id={'select_deposit_account'} />}
|
||||||
form.setFieldValue('deposit_account_id', account.id);
|
|
||||||
}}
|
|
||||||
defaultSelectText={<T id={'select_deposit_account'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
filterByTypes={[
|
filterByTypes={[
|
||||||
ACCOUNT_TYPE.CASH,
|
ACCOUNT_TYPE.CASH,
|
||||||
ACCOUNT_TYPE.BANK,
|
ACCOUNT_TYPE.BANK,
|
||||||
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||||
]}
|
]}
|
||||||
|
shouldUpdate={accountsFieldShouldUpdate}
|
||||||
|
fastField={true}
|
||||||
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ------------ Reference No. ------------ */}
|
{/* ------------ Reference No. ------------ */}
|
||||||
<FastField name={'reference_no'}>
|
<FastField name={'reference_no'}>
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export const customersFieldShouldUpdate = (newProps, oldProps) => {
|
|||||||
*/
|
*/
|
||||||
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
|
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
|
||||||
return (
|
return (
|
||||||
newProps.accounts !== oldProps.accounts ||
|
newProps.items !== oldProps.items ||
|
||||||
defaultFastFieldShouldUpdate(newProps, oldProps)
|
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { CLASSES } from '@/constants/classes';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
FFormGroup,
|
FFormGroup,
|
||||||
AccountsSelectList,
|
AccountsSelect,
|
||||||
CustomerSelectField,
|
CustomerSelectField,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
Icon,
|
Icon,
|
||||||
@@ -125,38 +125,30 @@ function ReceiptFormHeader({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* ----------- Deposit account ----------- */}
|
{/* ----------- Deposit account ----------- */}
|
||||||
<FastField
|
<FFormGroup
|
||||||
name={'deposit_account_id'}
|
|
||||||
accounts={accounts}
|
|
||||||
shouldUpdate={accountsFieldShouldUpdate}
|
|
||||||
>
|
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'deposit_account'} />}
|
label={<T id={'deposit_account'} />}
|
||||||
className={classNames('form-group--deposit-account', CLASSES.FILL)}
|
|
||||||
inline={true}
|
inline={true}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={inputIntent({ error, touched })}
|
name={'deposit_account_id'}
|
||||||
helperText={<ErrorMessage name={'deposit_account_id'} />}
|
items={accounts}
|
||||||
|
fastField={true}
|
||||||
|
shouldUpdate={accountsFieldShouldUpdate}
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={accounts}
|
items={accounts}
|
||||||
onAccountSelected={(account) => {
|
name={'deposit_account_id'}
|
||||||
form.setFieldValue('deposit_account_id', account.id);
|
placeholder={<T id={'select_deposit_account'} />}
|
||||||
}}
|
|
||||||
defaultSelectText={<T id={'select_deposit_account'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
popoverFill={true}
|
|
||||||
filterByTypes={[
|
filterByTypes={[
|
||||||
ACCOUNT_TYPE.CASH,
|
ACCOUNT_TYPE.CASH,
|
||||||
ACCOUNT_TYPE.BANK,
|
ACCOUNT_TYPE.BANK,
|
||||||
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||||
]}
|
]}
|
||||||
allowCreate={true}
|
allowCreate={true}
|
||||||
|
fill={true}
|
||||||
|
fastField={true}
|
||||||
|
shouldUpdate={accountsFieldShouldUpdate}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ----------- Receipt date ----------- */}
|
{/* ----------- Receipt date ----------- */}
|
||||||
<FastField name={'receipt_date'}>
|
<FastField name={'receipt_date'}>
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export const entriesFieldShouldUpdate = (newProps, oldProps) => {
|
|||||||
*/
|
*/
|
||||||
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
|
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
|
||||||
return (
|
return (
|
||||||
newProps.accounts !== oldProps.accounts ||
|
newProps.items !== oldProps.items ||
|
||||||
defaultFastFieldShouldUpdate(newProps, oldProps)
|
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
|||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const mapped = {
|
const mapped = {
|
||||||
|
allSettings: state.settings.data,
|
||||||
organizationSettings: state.settings.data.organization,
|
organizationSettings: state.settings.data.organization,
|
||||||
manualJournalsSettings: state.settings.data.manualJournals,
|
manualJournalsSettings: state.settings.data.manualJournals,
|
||||||
billPaymentSettings: state.settings.data.billPayments,
|
billPaymentSettings: state.settings.data.billPayments,
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FastField, Form, ErrorMessage } from 'formik';
|
import { FastField, Form, ErrorMessage } from 'formik';
|
||||||
import {
|
import { Button, Intent, FormGroup, Classes } from '@blueprintjs/core';
|
||||||
Button,
|
|
||||||
Intent,
|
|
||||||
FormGroup,
|
|
||||||
MenuItem,
|
|
||||||
Classes,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { TimezonePicker } from '@blueprintjs/timezone';
|
import { TimezonePicker } from '@blueprintjs/timezone';
|
||||||
import { FFormGroup, FInputGroup, FormattedMessage as T } from '@/components';
|
import {
|
||||||
|
FFormGroup,
|
||||||
|
FInputGroup,
|
||||||
|
FSelect,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from '@/components';
|
||||||
|
|
||||||
import { Col, Row, ListSelect } from '@/components';
|
import { Col, Row } from '@/components';
|
||||||
import { inputIntent } from '@/utils';
|
import { inputIntent } from '@/utils';
|
||||||
|
|
||||||
import { getFiscalYear } from '@/constants/fiscalYearOptions';
|
import { getFiscalYear } from '@/constants/fiscalYearOptions';
|
||||||
@@ -35,152 +34,88 @@ export default function SetupOrganizationForm({ isSubmitting, values }) {
|
|||||||
<h3>
|
<h3>
|
||||||
<T id={'organization_details'} />
|
<T id={'organization_details'} />
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* ---------- Organization name ---------- */}
|
{/* ---------- Organization name ---------- */}
|
||||||
<FFormGroup name={'name'} label={<T id={'legal_organization_name'} />}>
|
<FFormGroup
|
||||||
<FInputGroup name={'name'} />
|
name={'name'}
|
||||||
|
label={<T id={'legal_organization_name'} />}
|
||||||
|
fastField={true}
|
||||||
|
>
|
||||||
|
<FInputGroup name={'name'} fastField={true} />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
{/* ---------- Location ---------- */}
|
{/* ---------- Location ---------- */}
|
||||||
<FastField name={'location'}>
|
<FFormGroup
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
name={'location'}
|
||||||
<FormGroup
|
|
||||||
label={<T id={'business_location'} />}
|
label={<T id={'business_location'} />}
|
||||||
className={classNames(
|
fastField={true}
|
||||||
'form-group--business-location',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
helperText={<ErrorMessage name="location" />}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
>
|
>
|
||||||
<ListSelect
|
<FSelect
|
||||||
|
name={'location'}
|
||||||
items={countries}
|
items={countries}
|
||||||
onItemSelect={({ countryCode }) => {
|
valueAccessor={'countryCode'}
|
||||||
form.setFieldValue('location', countryCode);
|
textAccessor={'name'}
|
||||||
}}
|
placeholder={<T id={'select_business_location'} />}
|
||||||
selectedItem={value}
|
|
||||||
selectedItemProp={'countryCode'}
|
|
||||||
defaultText={<T id={'select_business_location'} />}
|
|
||||||
textProp={'name'}
|
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
|
fastField={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={6}>
|
<Col xs={6}>
|
||||||
{/* ---------- Base currency ---------- */}
|
{/* ---------- Base currency ---------- */}
|
||||||
<FastField name={'baseCurrency'}>
|
<FFormGroup
|
||||||
{({
|
name={'baseCurrency'}
|
||||||
form: { setFieldValue },
|
|
||||||
field: { value },
|
|
||||||
meta: { error, touched },
|
|
||||||
}) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'base_currency'} />}
|
label={<T id={'base_currency'} />}
|
||||||
className={classNames(
|
fastField={true}
|
||||||
'form-group--base-currency',
|
|
||||||
'form-group--select-list',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name={'baseCurrency'} />}
|
|
||||||
>
|
>
|
||||||
<ListSelect
|
<FSelect
|
||||||
|
name={'baseCurrency'}
|
||||||
items={currencies}
|
items={currencies}
|
||||||
noResults={
|
|
||||||
<MenuItem disabled={true} text={<T id={'no_results'} />} />
|
|
||||||
}
|
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
onItemSelect={(item) => {
|
valueAccessor={'key'}
|
||||||
setFieldValue('baseCurrency', item.key);
|
textAccessor={'name'}
|
||||||
}}
|
placeholder={<T id={'select_base_currency'} />}
|
||||||
selectedItemProp={'key'}
|
fastField={true}
|
||||||
textProp={'name'}
|
|
||||||
defaultText={<T id={'select_base_currency'} />}
|
|
||||||
selectedItem={value}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
{/* ---------- Language ---------- */}
|
{/* ---------- Language ---------- */}
|
||||||
<Col xs={6}>
|
<Col xs={6}>
|
||||||
<FastField name={'language'}>
|
<FFormGroup
|
||||||
{({
|
name={'language'}
|
||||||
form: { setFieldValue },
|
|
||||||
field: { value },
|
|
||||||
meta: { error, touched },
|
|
||||||
}) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'language'} />}
|
label={<T id={'language'} />}
|
||||||
className={classNames(
|
fastField={true}
|
||||||
'form-group--language',
|
|
||||||
'form-group--select-list',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name={'language'} />}
|
|
||||||
>
|
>
|
||||||
<ListSelect
|
<FSelect
|
||||||
|
name={'language'}
|
||||||
items={Languages}
|
items={Languages}
|
||||||
noResults={
|
valueAccessor={'value'}
|
||||||
<MenuItem disabled={true} text={<T id={'no_results'} />} />
|
textAccessor={'name'}
|
||||||
}
|
placeholder={<T id={'select_language'} />}
|
||||||
onItemSelect={(item) => {
|
|
||||||
setFieldValue('language', item.value);
|
|
||||||
}}
|
|
||||||
selectedItem={value}
|
|
||||||
textProp={'name'}
|
|
||||||
selectedItemProp={'value'}
|
|
||||||
defaultText={<T id={'select_language'} />}
|
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
filterable={false}
|
fastField={true}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{/* --------- Fiscal Year ----------- */}
|
{/* --------- Fiscal Year ----------- */}
|
||||||
<FastField name={'fiscalYear'}>
|
<FFormGroup
|
||||||
{({
|
name={'fiscalYear'}
|
||||||
form: { setFieldValue },
|
|
||||||
field: { value },
|
|
||||||
meta: { error, touched },
|
|
||||||
}) => (
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'fiscal_year'} />}
|
label={<T id={'fiscal_year'} />}
|
||||||
className={classNames(
|
fastField={true}
|
||||||
'form-group--fiscal_year',
|
|
||||||
'form-group--select-list',
|
|
||||||
Classes.FILL,
|
|
||||||
)}
|
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name={'fiscalYear'} />}
|
|
||||||
>
|
>
|
||||||
<ListSelect
|
<FSelect
|
||||||
|
name={'fiscalYear'}
|
||||||
items={FiscalYear}
|
items={FiscalYear}
|
||||||
noResults={
|
valueAccessor={'key'}
|
||||||
<MenuItem disabled={true} text={<T id={'no_results'} />} />
|
textAccessor={'name'}
|
||||||
}
|
placeholder={<T id={'select_fiscal_year'} />}
|
||||||
selectedItem={value}
|
|
||||||
selectedItemProp={'key'}
|
|
||||||
textProp={'name'}
|
|
||||||
defaultText={<T id={'select_fiscal_year'} />}
|
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
onItemSelect={(item) => {
|
fastField={true}
|
||||||
setFieldValue('fiscalYear', item.key);
|
|
||||||
}}
|
|
||||||
filterable={false}
|
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
|
|
||||||
{/* ---------- Time zone ---------- */}
|
{/* ---------- Time zone ---------- */}
|
||||||
<FastField name={'timezone'}>
|
<FastField name={'timezone'}>
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ function VendorsListProvider({ tableState, tableStateChanged, ...props }) {
|
|||||||
isFetching: isVendorsFetching,
|
isFetching: isVendorsFetching,
|
||||||
} = useVendors(tableQuery, { keepPreviousData: true });
|
} = useVendors(tableQuery, { keepPreviousData: true });
|
||||||
|
|
||||||
// Fetch customers resource views and fields.
|
// Fetch vendors resource views and fields.
|
||||||
const { data: vendorsViews, isLoading: isVendorsViewsLoading } =
|
const { data: vendorsViews, isLoading: isVendorsViewsLoading } =
|
||||||
useResourceViews('vendors');
|
useResourceViews('vendors');
|
||||||
|
|
||||||
// Fetch the customers resource fields.
|
// Fetch the vendors resource fields.
|
||||||
const {
|
const {
|
||||||
data: resourceMeta,
|
data: resourceMeta,
|
||||||
isLoading: isResourceMetaLoading,
|
isLoading: isResourceMetaLoading,
|
||||||
isFetching: isResourceMetaFetching,
|
isFetching: isResourceMetaFetching,
|
||||||
} = useResourceMeta('customers');
|
} = useResourceMeta('vendors');
|
||||||
|
|
||||||
// Detarmines the datatable empty status.
|
// Detarmines the datatable empty status.
|
||||||
const isEmptyStatus =
|
const isEmptyStatus =
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ import {
|
|||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { DateInput } from '@blueprintjs/datetime';
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
import { FastField, Field, ErrorMessage } from 'formik';
|
import { FastField, Field, ErrorMessage } from 'formik';
|
||||||
import { FormattedMessage as T } from '@/components';
|
import { FFormGroup, FormattedMessage as T } from '@/components';
|
||||||
import { momentFormatter, compose, tansformDateValue } from '@/utils';
|
import { momentFormatter, compose, tansformDateValue } from '@/utils';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
import {
|
import {
|
||||||
AccountsSelectList,
|
AccountsSelect,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
Icon,
|
Icon,
|
||||||
InputPrependButton,
|
InputPrependButton,
|
||||||
@@ -91,6 +91,7 @@ function WarehouseTransferFormHeaderFields({
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
{/* ----------- Transfer number ----------- */}
|
{/* ----------- Transfer number ----------- */}
|
||||||
<Field name={'transaction_number'}>
|
<Field name={'transaction_number'}>
|
||||||
{({ form, field, meta: { error, touched } }) => (
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
@@ -130,60 +131,39 @@ function WarehouseTransferFormHeaderFields({
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
{/* ----------- Form Warehouse ----------- */}
|
{/* ----------- Form Warehouse ----------- */}
|
||||||
<FastField name={'from_warehouse_id'} accounts={warehouses}>
|
<FFormGroup
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
name={'from_warehouse_id'}
|
||||||
<FormGroup
|
items={warehouses}
|
||||||
label={<T id={'warehouse_transfer.label.from_warehouse'} />}
|
label={<T id={'warehouse_transfer.label.from_warehouse'} />}
|
||||||
className={classNames(
|
|
||||||
'form-group--warehouse-transfer',
|
|
||||||
CLASSES.FILL,
|
|
||||||
)}
|
|
||||||
inline={true}
|
inline={true}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name={'from_warehouse_id'} />}
|
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={warehouses}
|
name={'from_warehouse_id'}
|
||||||
onAccountSelected={(account) => {
|
items={warehouses}
|
||||||
form.setFieldValue('from_warehouse_id', account.id);
|
placeholder={<T id={'select_warehouse_transfer'} />}
|
||||||
}}
|
|
||||||
defaultSelectText={<T id={'select_warehouse_transfer'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
popoverFill={true}
|
|
||||||
allowCreate={true}
|
allowCreate={true}
|
||||||
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
{/* ----------- To Warehouse ----------- */}
|
{/* ----------- To Warehouse ----------- */}
|
||||||
<FastField name={'to_warehouse_id'} accounts={warehouses}>
|
<FFormGroup
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
name={'to_warehouse_id'}
|
||||||
<FormGroup
|
|
||||||
label={<T id={'warehouse_transfer.label.to_warehouse'} />}
|
label={<T id={'warehouse_transfer.label.to_warehouse'} />}
|
||||||
className={classNames(
|
|
||||||
'form-group--warehouse-transfer',
|
|
||||||
CLASSES.FILL,
|
|
||||||
)}
|
|
||||||
inline={true}
|
inline={true}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={inputIntent({ error, touched })}
|
|
||||||
helperText={<ErrorMessage name={'to_warehouse_id'} />}
|
|
||||||
>
|
>
|
||||||
<AccountsSelectList
|
<AccountsSelect
|
||||||
accounts={warehouses}
|
name={'to_warehouse_id'}
|
||||||
onAccountSelected={(account) => {
|
items={warehouses}
|
||||||
form.setFieldValue('to_warehouse_id', account.id);
|
placeholder={<T id={'select_warehouse_transfer'} />}
|
||||||
}}
|
fill={true}
|
||||||
defaultSelectText={<T id={'select_warehouse_transfer'} />}
|
|
||||||
selectedAccountId={value}
|
|
||||||
popoverFill={true}
|
|
||||||
allowCreate={true}
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
)}
|
|
||||||
</FastField>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
import useApiRequest from '../useRequest';
|
import useApiRequest from '../useRequest';
|
||||||
import { setCookie } from '../../utils';
|
import { setCookie } from '../../utils';
|
||||||
|
import { useRequestQuery } from '../useQueryRequest';
|
||||||
|
import t from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the response data to cookies.
|
* Saves the response data to cookies.
|
||||||
@@ -70,3 +72,21 @@ export const useAuthResetPassword = (props) => {
|
|||||||
props,
|
props,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the authentication page metadata.
|
||||||
|
*/
|
||||||
|
export const useAuthMetadata = (props) => {
|
||||||
|
return useRequestQuery(
|
||||||
|
[t.AUTH_METADATA_PAGE,],
|
||||||
|
{
|
||||||
|
method: 'get',
|
||||||
|
url: `auth/meta`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
select: (res) => res.data,
|
||||||
|
defaultData: {},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
const Authentication = {
|
||||||
|
AUTH_METADATA_PAGE: 'AUTH_META_PAGE'
|
||||||
|
}
|
||||||
|
|
||||||
const ACCOUNTS = {
|
const ACCOUNTS = {
|
||||||
ACCOUNT: 'ACCOUNT',
|
ACCOUNT: 'ACCOUNT',
|
||||||
ACCOUNT_TRANSACTION: 'ACCOUNT_TRANSACTION',
|
ACCOUNT_TRANSACTION: 'ACCOUNT_TRANSACTION',
|
||||||
@@ -217,6 +221,7 @@ const DASHBOARD = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
...Authentication,
|
||||||
...ACCOUNTS,
|
...ACCOUNTS,
|
||||||
...BILLS,
|
...BILLS,
|
||||||
...VENDORS,
|
...VENDORS,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
"new_currency": "New Currency",
|
"new_currency": "New Currency",
|
||||||
"currency_name": "Currency Name",
|
"currency_name": "Currency Name",
|
||||||
"currency_code": "Currency Code",
|
"currency_code": "Currency Code",
|
||||||
"select_currency_code": "Select Currency Code",
|
"select_currency_code": "Select Currency Code...",
|
||||||
"edit_exchange_rate": "Edit Exchange Rate",
|
"edit_exchange_rate": "Edit Exchange Rate",
|
||||||
"new_exchange_rate": "New Exchange Rate",
|
"new_exchange_rate": "New Exchange Rate",
|
||||||
"delete_exchange_rate": "Delete Exchange Rate",
|
"delete_exchange_rate": "Delete Exchange Rate",
|
||||||
@@ -242,7 +242,7 @@
|
|||||||
"organization": "Organization.",
|
"organization": "Organization.",
|
||||||
"check_your_email_for_a_link_to_reset": "Check your email for a link to reset your password.If it doesn’t appear within a few minutes, check your spam folder.",
|
"check_your_email_for_a_link_to_reset": "Check your email for a link to reset your password.If it doesn’t appear within a few minutes, check your spam folder.",
|
||||||
"we_couldn_t_find_your_account_with_that_email": "We couldn't find your account with that email.",
|
"we_couldn_t_find_your_account_with_that_email": "We couldn't find your account with that email.",
|
||||||
"select_parent_account": "Select Parent Account",
|
"select_parent_account": "Select Parent Account...",
|
||||||
"the_exchange_rate_has_been_edited_successfully": "The exchange rate has been edited successfully",
|
"the_exchange_rate_has_been_edited_successfully": "The exchange rate has been edited successfully",
|
||||||
"the_exchange_rate_has_been_created_successfully": "The exchange rate has been created successfully",
|
"the_exchange_rate_has_been_created_successfully": "The exchange rate has been created successfully",
|
||||||
"the_user_details_has_been_updated": "The user details has been updated",
|
"the_user_details_has_been_updated": "The user details has been updated",
|
||||||
@@ -540,7 +540,7 @@
|
|||||||
"statement": "Statement",
|
"statement": "Statement",
|
||||||
"deposit_account": "Deposit Account",
|
"deposit_account": "Deposit Account",
|
||||||
"send_to_email": "Send to email",
|
"send_to_email": "Send to email",
|
||||||
"select_deposit_account": "Select Deposit Account",
|
"select_deposit_account": "Select Deposit Account...",
|
||||||
"once_delete_this_receipt_you_will_able_to_restore_it": "Once you delete this receipt, you won't be able to restore it later. Are you sure you want to delete this receipt?",
|
"once_delete_this_receipt_you_will_able_to_restore_it": "Once you delete this receipt, you won't be able to restore it later. Are you sure you want to delete this receipt?",
|
||||||
"the_receipt_has_been_created_successfully": "The receipt #{number} has been created successfully.",
|
"the_receipt_has_been_created_successfully": "The receipt #{number} has been created successfully.",
|
||||||
"the_receipt_has_been_edited_successfully": "The receipt #{number} has been edited successfully.",
|
"the_receipt_has_been_edited_successfully": "The receipt #{number} has been edited successfully.",
|
||||||
@@ -609,8 +609,8 @@
|
|||||||
"new_payment_made": "New Payment Made",
|
"new_payment_made": "New Payment Made",
|
||||||
"payment_made_list": "Payment Made List",
|
"payment_made_list": "Payment Made List",
|
||||||
"payment_account": "Payment Account",
|
"payment_account": "Payment Account",
|
||||||
"select_vender_account": "Select Vender Account",
|
"select_vender_account": "Select Vender Account...",
|
||||||
"select_payment_account": "Select Payment Account",
|
"select_payment_account": "Select Payment Account...",
|
||||||
"the_payment_made_has_been_edited_successfully": "The payment made has been edited successfully.",
|
"the_payment_made_has_been_edited_successfully": "The payment made has been edited successfully.",
|
||||||
"the_payment_made_has_been_created_successfully": "The payment made has been created successfully.",
|
"the_payment_made_has_been_created_successfully": "The payment made has been created successfully.",
|
||||||
"the_payment_made_has_been_deleted_successfully": "The payment made has been deleted successfully.",
|
"the_payment_made_has_been_deleted_successfully": "The payment made has been deleted successfully.",
|
||||||
@@ -1417,6 +1417,7 @@
|
|||||||
"siebar.cashflow": "Cash flow",
|
"siebar.cashflow": "Cash flow",
|
||||||
"siebar.cashflow.label_cash_and_bank_accounts": "Cash & Bank Accounts",
|
"siebar.cashflow.label_cash_and_bank_accounts": "Cash & Bank Accounts",
|
||||||
"cash_flow.label_account_transcations": "Account Transcations",
|
"cash_flow.label_account_transcations": "Account Transcations",
|
||||||
|
"cash_flow.transactions_for_review": "Transactions for review",
|
||||||
"cash_flow.label.deposit": "Deposit",
|
"cash_flow.label.deposit": "Deposit",
|
||||||
"cash_flow.label.withdrawal": "Withdrawal",
|
"cash_flow.label.withdrawal": "Withdrawal",
|
||||||
"cash_flow.label.running_balance": "Running balance",
|
"cash_flow.label.running_balance": "Running balance",
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ body.hide-scrollbar .Pane2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bp3-fill {
|
.bp3-fill {
|
||||||
|
|
||||||
.bp3-popover-wrapper,
|
.bp3-popover-wrapper,
|
||||||
.bp3-popover-target {
|
.bp3-popover-target {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -68,9 +69,9 @@ body.hide-scrollbar .Pane2 {
|
|||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bp3-select-popover .bp3-menu {
|
.bp3-select-popover .bp3-menu,
|
||||||
|
.bp3-multi-select-popover .bp3-menu {
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
max-width: 400px;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,27 +104,21 @@ body.hide-scrollbar .Pane2 {
|
|||||||
background-color: #0066ff;
|
background-color: #0066ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ReactVirtualized__Collection {
|
.ReactVirtualized__Collection {}
|
||||||
}
|
|
||||||
|
|
||||||
.ReactVirtualized__Collection__innerScrollContainer {
|
.ReactVirtualized__Collection__innerScrollContainer {}
|
||||||
}
|
|
||||||
|
|
||||||
/* Grid default theme */
|
/* Grid default theme */
|
||||||
|
|
||||||
.ReactVirtualized__Grid {
|
.ReactVirtualized__Grid {}
|
||||||
}
|
|
||||||
|
|
||||||
.ReactVirtualized__Grid__innerScrollContainer {
|
.ReactVirtualized__Grid__innerScrollContainer {}
|
||||||
}
|
|
||||||
|
|
||||||
/* Table default theme */
|
/* Table default theme */
|
||||||
|
|
||||||
.ReactVirtualized__Table {
|
.ReactVirtualized__Table {}
|
||||||
}
|
|
||||||
|
|
||||||
.ReactVirtualized__Table__Grid {
|
.ReactVirtualized__Table__Grid {}
|
||||||
}
|
|
||||||
|
|
||||||
.ReactVirtualized__Table__headerRow {
|
.ReactVirtualized__Table__headerRow {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -186,8 +181,7 @@ body.hide-scrollbar .Pane2 {
|
|||||||
|
|
||||||
/* List default theme */
|
/* List default theme */
|
||||||
|
|
||||||
.ReactVirtualized__List {
|
.ReactVirtualized__List {}
|
||||||
}
|
|
||||||
|
|
||||||
.bp3-drawer {
|
.bp3-drawer {
|
||||||
box-shadow: 0 0 0;
|
box-shadow: 0 0 0;
|
||||||
@@ -243,7 +237,7 @@ html[lang^='ar'] {
|
|||||||
padding-left: 14px;
|
padding-left: 14px;
|
||||||
padding-right: 14px;
|
padding-right: 14px;
|
||||||
|
|
||||||
& + .bp3-button {
|
&+.bp3-button {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,7 +249,7 @@ html[lang^='ar'] {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .bp3-spinner {
|
>.bp3-spinner {
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,6 +277,7 @@ html[lang^='ar'] {
|
|||||||
.align-right {
|
.align-right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.align-center {
|
.align-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -292,6 +287,6 @@ html[lang^='ar'] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
span.table-tooltip-overview-target{
|
span.table-tooltip-overview-target {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user