mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-10 18:01:59 +00:00
Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
130008168a | ||
|
|
7de66f16ce | ||
|
|
9b9b43d065 | ||
|
|
048fcda9dc | ||
|
|
f9a7021f55 | ||
|
|
41db96d958 | ||
|
|
900a237a52 | ||
|
|
6363576c5e | ||
|
|
3c3f8c6731 | ||
|
|
8ac881cfd7 | ||
|
|
6055184084 | ||
|
|
b83faef167 | ||
|
|
d102f33698 | ||
|
|
942644f8d5 | ||
|
|
20d9a23260 | ||
|
|
7ac9f78366 | ||
|
|
777f6a11cd | ||
|
|
785c407a73 | ||
|
|
860378f00d | ||
|
|
01e2c24387 | ||
|
|
84a903361e | ||
|
|
2ae720821e | ||
|
|
c75f46d8a4 | ||
|
|
d77fcb7c89 | ||
|
|
7c6a85d1d4 | ||
|
|
dd264eed65 | ||
|
|
6b37dcd8ae | ||
|
|
5c3a7effc1 | ||
|
|
69c4519647 | ||
|
|
95137f4fcd | ||
|
|
7c0aa9b353 | ||
|
|
3753097ea6 | ||
|
|
93da3ed41d | ||
|
|
c5a6a72fa5 | ||
|
|
4cb7a50a1d | ||
|
|
5c8c3586e0 | ||
|
|
2e7ba59beb | ||
|
|
7350fef5c4 | ||
|
|
1b13b98899 | ||
|
|
72c893c255 | ||
|
|
fcf001a831 | ||
|
|
a17843ddbe | ||
|
|
399ba8fb8e | ||
|
|
f00097f6c8 | ||
|
|
cd08d0ee16 | ||
|
|
f268b8a95a | ||
|
|
6a06950654 | ||
|
|
d9de3341fe | ||
|
|
6b6081e32e | ||
|
|
7be568b8ac | ||
|
|
50522af72d | ||
|
|
0b454d6d4d | ||
|
|
4ba64cc4ff | ||
|
|
5128c021b0 | ||
|
|
5a8fcc8fb5 | ||
|
|
9cf1b993dd | ||
|
|
f443a1b106 | ||
|
|
0eb0aee1ef | ||
|
|
4b992c4bb4 | ||
|
|
051681e6f3 | ||
|
|
629c790430 | ||
|
|
bdadc5d795 | ||
|
|
23bb9c4cc3 | ||
|
|
8136378725 | ||
|
|
4eac2239b1 | ||
|
|
a44f548ff9 | ||
|
|
327916da4b | ||
|
|
bee7896279 | ||
|
|
cb0a315ca6 | ||
|
|
d2c907541a | ||
|
|
928d4d3f00 | ||
|
|
4d00f53600 | ||
|
|
5e293e4f19 | ||
|
|
01038136f2 | ||
|
|
6bc5eec8b6 | ||
|
|
1172e69d96 | ||
|
|
87758bf773 | ||
|
|
5cbb3c84e6 | ||
|
|
52924383bd | ||
|
|
8d1825a065 | ||
|
|
5e4e9c37c3 | ||
|
|
944bc29f4d | ||
|
|
682b296f7c | ||
|
|
e662bf7af9 | ||
|
|
a829ceb709 | ||
|
|
cdce00187b | ||
|
|
91a38b34cc | ||
|
|
1b97a162e8 | ||
|
|
e8e12e63ea | ||
|
|
80feba6005 | ||
|
|
cc457e1e43 | ||
|
|
2ced5dc013 | ||
|
|
dd86c2993e | ||
|
|
bd05a4a188 | ||
|
|
8160cbe402 | ||
|
|
0ef6bebfb8 | ||
|
|
91ff3fdccb | ||
|
|
edd37fff78 | ||
|
|
679f7ce96c | ||
|
|
5eb9968095 | ||
|
|
5c601fcf2d | ||
|
|
df4c4a832b | ||
|
|
98a02396a9 | ||
|
|
96635ffa84 | ||
|
|
68c0678dc3 | ||
|
|
da699a766a | ||
|
|
f1899e1ce1 | ||
|
|
11851d114d | ||
|
|
21779007be | ||
|
|
4fc1ecdc2d | ||
|
|
c9b5cecf7a | ||
|
|
c31e9dcd29 | ||
|
|
430ab95dc3 | ||
|
|
8100a57195 |
@@ -1 +1,3 @@
|
||||
APP_VERSION=$npm_package_version
|
||||
REACT_APP_VERSION=$npm_package_version
|
||||
TSC_COMPILE_ON_ERROR=true
|
||||
ESLINT_NO_DEV_ERRORS=true
|
||||
13
.storybook/main.js
Normal file
13
.storybook/main.js
Normal file
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
"stories": [
|
||||
"../src/**/*.stories.mdx",
|
||||
"../src/**/*.stories.@(js|jsx|ts|tsx)"
|
||||
],
|
||||
"addons": [
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-interactions",
|
||||
"@storybook/preset-create-react-app"
|
||||
],
|
||||
"framework": "@storybook/react"
|
||||
}
|
||||
9
.storybook/preview.js
Normal file
9
.storybook/preview.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
}
|
||||
101
CHANGELOG.md
101
CHANGELOG.md
@@ -2,66 +2,95 @@
|
||||
|
||||
All notable changes to Bigcapital server-side will be in this file.
|
||||
|
||||
## [1.7.6-rc.2] - 23-04-2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- `BIG-374` Refactoring sidebar men with ability permissions and feature control on each item.
|
||||
|
||||
## [v1.7.5-rc.2] - 20-04-2022
|
||||
|
||||
### Fixed.
|
||||
|
||||
- `BIG-378` Reports drawers columns css conflict.
|
||||
|
||||
## [1.7.3-rc.2] - 15-04-2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- `BIG-372` Activate branches and warehouses dialog reloading once activating.
|
||||
- `BIG-373` Issue general ledger report select specific account.
|
||||
- `BIG-377` Make readonly details entries as oneline with tooltip for more details.
|
||||
|
||||
## [1.7.2-rc.2] - 04-04-2022
|
||||
|
||||
### Fixed
|
||||
- Add the missing Arabic localization.
|
||||
- Subscription plans modifications.
|
||||
|
||||
- Add the missing Arabic localization.
|
||||
- Subscription plans modifications.
|
||||
|
||||
## [1.7.1-rc.2] - 30-03-2022
|
||||
|
||||
## Added
|
||||
- `BIG-141` Add inactive status to item drawer details.
|
||||
- `BIG-278` Add created at date on expense details.
|
||||
- `BIG-350` Add empty status content of warehouse transfers service.
|
||||
- `BIG-344` Add branch details to manual journal and expense details.
|
||||
|
||||
- `BIG-141` Add inactive status to item drawer details.
|
||||
- `BIG-278` Add created at date on expense details.
|
||||
- `BIG-350` Add empty status content of warehouse transfers service.
|
||||
- `BIG-344` Add branch details to manual journal and expense details.
|
||||
|
||||
## Fixed
|
||||
- `BIG-221` Remove Non-inventory radio choice on item form.
|
||||
- `BIG-236` Validate estimate expiration date should be equal or bigger than estimate date.
|
||||
- `BIG-237` Validate invoice due date should be equal or bigger than invoice date.
|
||||
- `BIG-238` Validate bill due date should be equal or bigger than bill date.
|
||||
- `BIG-280` Optimize style of multi-select accounts menu.
|
||||
- `BIG-284` Cashflow statement loading bar.
|
||||
- `BIG-296` Creating a new child account from accounts list.
|
||||
- `BIG-301` Navigation bar divider on actions bar hide with permissions control.
|
||||
- `BIG-304` Adding cash or bank account from cash flow service.
|
||||
- `BIG-351` Invalid date in the inventory adjustment detail.
|
||||
- `BIG-352` Fix terms and notes fields on footer of all services.
|
||||
- `BIG-354` Validate the warehouse transfer quantity should be above zero.
|
||||
|
||||
- `BIG-221` Remove Non-inventory radio choice on item form.
|
||||
- `BIG-236` Validate estimate expiration date should be equal or bigger than estimate date.
|
||||
- `BIG-237` Validate invoice due date should be equal or bigger than invoice date.
|
||||
- `BIG-238` Validate bill due date should be equal or bigger than bill date.
|
||||
- `BIG-280` Optimize style of multi-select accounts menu.
|
||||
- `BIG-284` Cashflow statement loading bar.
|
||||
- `BIG-296` Creating a new child account from accounts list.
|
||||
- `BIG-301` Navigation bar divider on actions bar hide with permissions control.
|
||||
- `BIG-304` Adding cash or bank account from cash flow service.
|
||||
- `BIG-351` Invalid date in the inventory adjustment detail.
|
||||
- `BIG-352` Fix terms and notes fields on footer of all services.
|
||||
- `BIG-354` Validate the warehouse transfer quantity should be above zero.
|
||||
|
||||
## [1.7.0-rc.1] - 24-03-2022
|
||||
|
||||
## Added
|
||||
- Multiply currencies with foreign currencies.
|
||||
- Multiply warehouses to track inventory items.
|
||||
- Multiply branches to track organization transactions.
|
||||
- Transfer orders between warehouses.
|
||||
- Integrate financial reports with multiply branches.
|
||||
- Integrate inventory reports with multiply warehouses.
|
||||
## Added
|
||||
|
||||
- Multiply currencies with foreign currencies.
|
||||
- Multiply warehouses to track inventory items.
|
||||
- Multiply branches to track organization transactions.
|
||||
- Transfer orders between warehouses.
|
||||
- Integrate financial reports with multiply branches.
|
||||
- Integrate inventory reports with multiply warehouses.
|
||||
|
||||
## Changes
|
||||
- Optimize style of sale invoice form.
|
||||
- Optimize style of sale receipt form.
|
||||
- Optimize style of credit note form.
|
||||
- Optimize style of payment receive form.
|
||||
- Optimize style of bill form.
|
||||
- Optimize style of payment made form.
|
||||
- Optimize style of manual journal form.
|
||||
- Optimize style of expense form.
|
||||
|
||||
- Optimize style of sale invoice form.
|
||||
- Optimize style of sale receipt form.
|
||||
- Optimize style of credit note form.
|
||||
- Optimize style of payment receive form.
|
||||
- Optimize style of bill form.
|
||||
- Optimize style of payment made form.
|
||||
- Optimize style of manual journal form.
|
||||
- Optimize style of expense form.
|
||||
|
||||
## [1.6.3] - 21-02-2022
|
||||
|
||||
### Fixed
|
||||
- `BIG-337` Display billing page once the organization subscription is inactive.
|
||||
|
||||
- `BIG-337` Display billing page once the organization subscription is inactive.
|
||||
|
||||
## [1.6.2] - 19-02-2022
|
||||
|
||||
### Fixed
|
||||
- fix syled components dependency with imported as default components.
|
||||
|
||||
- fix syled components dependency with imported as default components.
|
||||
|
||||
## [1.6.0] - 18-02-2022
|
||||
|
||||
### Added
|
||||
|
||||
- Balance sheet comparison of previous period (PP).
|
||||
- Balance sheet comparison of previous year (PY).
|
||||
- Balance sheet percentage analysis columns and rows basis.
|
||||
@@ -72,10 +101,12 @@ All notable changes to Bigcapital server-side will be in this file.
|
||||
## [1.5.8] - 13-01-2022
|
||||
|
||||
### Added
|
||||
|
||||
- Add payment receive PDF print.
|
||||
- Add credit note PDF print.
|
||||
|
||||
### Fixed
|
||||
|
||||
- fix: Payment receive initial loading state depends on request loading state instead fetching.
|
||||
- fix: Balance sheet report alert positioning.
|
||||
- fix: Separate customer and vendor inactivate and activate alerts.
|
||||
|
||||
101
config/env.js
101
config/env.js
@@ -1,101 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const paths = require('./paths');
|
||||
|
||||
// Make sure that including paths.js after env.js will read .env variables.
|
||||
delete require.cache[require.resolve('./paths')];
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
if (!NODE_ENV) {
|
||||
throw new Error(
|
||||
'The NODE_ENV environment variable is required but was not specified.'
|
||||
);
|
||||
}
|
||||
|
||||
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
|
||||
const dotenvFiles = [
|
||||
`${paths.dotenv}.${NODE_ENV}.local`,
|
||||
`${paths.dotenv}.${NODE_ENV}`,
|
||||
// Don't include `.env.local` for `test` environment
|
||||
// since normally you expect tests to produce the same
|
||||
// results for everyone
|
||||
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
|
||||
paths.dotenv,
|
||||
].filter(Boolean);
|
||||
|
||||
// Load environment variables from .env* files. Suppress warnings using silent
|
||||
// if this file is missing. dotenv will never modify any environment variables
|
||||
// that have already been set. Variable expansion is supported in .env files.
|
||||
// https://github.com/motdotla/dotenv
|
||||
// https://github.com/motdotla/dotenv-expand
|
||||
dotenvFiles.forEach(dotenvFile => {
|
||||
if (fs.existsSync(dotenvFile)) {
|
||||
require('dotenv-expand')(
|
||||
require('dotenv').config({
|
||||
path: dotenvFile,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// We support resolving modules according to `NODE_PATH`.
|
||||
// This lets you use absolute paths in imports inside large monorepos:
|
||||
// https://github.com/facebook/create-react-app/issues/253.
|
||||
// It works similar to `NODE_PATH` in Node itself:
|
||||
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
|
||||
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
|
||||
// Otherwise, we risk importing Node.js core modules into an app instead of webpack shims.
|
||||
// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
|
||||
// We also resolve them to make sure all tools using them work consistently.
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
process.env.NODE_PATH = (process.env.NODE_PATH || '')
|
||||
.split(path.delimiter)
|
||||
.filter(folder => folder && !path.isAbsolute(folder))
|
||||
.map(folder => path.resolve(appDirectory, folder))
|
||||
.join(path.delimiter);
|
||||
|
||||
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
|
||||
// injected into the application via DefinePlugin in webpack configuration.
|
||||
const REACT_APP = /^REACT_APP_/i;
|
||||
|
||||
function getClientEnvironment(publicUrl) {
|
||||
const raw = Object.keys(process.env)
|
||||
.filter(key => REACT_APP.test(key))
|
||||
.reduce(
|
||||
(env, key) => {
|
||||
env[key] = process.env[key];
|
||||
return env;
|
||||
},
|
||||
{
|
||||
// Useful for determining whether we’re running in production mode.
|
||||
// Most importantly, it switches React into the correct mode.
|
||||
NODE_ENV: process.env.NODE_ENV || 'development',
|
||||
// Useful for resolving the correct path to static assets in `public`.
|
||||
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
|
||||
// This should only be used as an escape hatch. Normally you would put
|
||||
// images into the `src` and `import` them in code to get their paths.
|
||||
PUBLIC_URL: publicUrl,
|
||||
// We support configuring the sockjs pathname during development.
|
||||
// These settings let a developer run multiple simultaneous projects.
|
||||
// They are used as the connection `hostname`, `pathname` and `port`
|
||||
// in webpackHotDevClient. They are used as the `sockHost`, `sockPath`
|
||||
// and `sockPort` options in webpack-dev-server.
|
||||
WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST,
|
||||
WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
|
||||
WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
|
||||
}
|
||||
);
|
||||
// Stringify all values so we can feed into webpack DefinePlugin
|
||||
const stringified = {
|
||||
'process.env': Object.keys(raw).reduce((env, key) => {
|
||||
env[key] = JSON.stringify(raw[key]);
|
||||
return env;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
return { raw, stringified };
|
||||
}
|
||||
|
||||
module.exports = getClientEnvironment;
|
||||
@@ -1,66 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const chalk = require('react-dev-utils/chalk');
|
||||
const paths = require('./paths');
|
||||
|
||||
// Ensure the certificate and key provided are valid and if not
|
||||
// throw an easy to debug error
|
||||
function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
|
||||
let encrypted;
|
||||
try {
|
||||
// publicEncrypt will throw an error with an invalid cert
|
||||
encrypted = crypto.publicEncrypt(cert, Buffer.from('test'));
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// privateDecrypt will throw an error with an invalid key
|
||||
crypto.privateDecrypt(key, encrypted);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${
|
||||
err.message
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Read file and throw an error if it doesn't exist
|
||||
function readEnvFile(file, type) {
|
||||
if (!fs.existsSync(file)) {
|
||||
throw new Error(
|
||||
`You specified ${chalk.cyan(
|
||||
type
|
||||
)} in your env, but the file "${chalk.yellow(file)}" can't be found.`
|
||||
);
|
||||
}
|
||||
return fs.readFileSync(file);
|
||||
}
|
||||
|
||||
// Get the https config
|
||||
// Return cert files if provided in env, otherwise just true or false
|
||||
function getHttpsConfig() {
|
||||
const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env;
|
||||
const isHttps = HTTPS === 'true';
|
||||
|
||||
if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) {
|
||||
const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE);
|
||||
const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE);
|
||||
const config = {
|
||||
cert: readEnvFile(crtFile, 'SSL_CRT_FILE'),
|
||||
key: readEnvFile(keyFile, 'SSL_KEY_FILE'),
|
||||
};
|
||||
|
||||
validateKeyAndCerts({ ...config, keyFile, crtFile });
|
||||
return config;
|
||||
}
|
||||
return isHttps;
|
||||
}
|
||||
|
||||
module.exports = getHttpsConfig;
|
||||
@@ -1,14 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// This is a custom Jest transformer turning style imports into empty objects.
|
||||
// http://facebook.github.io/jest/docs/en/webpack.html
|
||||
|
||||
module.exports = {
|
||||
process() {
|
||||
return 'module.exports = {};';
|
||||
},
|
||||
getCacheKey() {
|
||||
// The output is always the same.
|
||||
return 'cssTransform';
|
||||
},
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const camelcase = require('camelcase');
|
||||
|
||||
// This is a custom Jest transformer turning file imports into filenames.
|
||||
// http://facebook.github.io/jest/docs/en/webpack.html
|
||||
|
||||
module.exports = {
|
||||
process(src, filename) {
|
||||
const assetFilename = JSON.stringify(path.basename(filename));
|
||||
|
||||
if (filename.match(/\.svg$/)) {
|
||||
// Based on how SVGR generates a component name:
|
||||
// https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
|
||||
const pascalCaseFilename = camelcase(path.parse(filename).name, {
|
||||
pascalCase: true,
|
||||
});
|
||||
const componentName = `Svg${pascalCaseFilename}`;
|
||||
return `const React = require('react');
|
||||
module.exports = {
|
||||
__esModule: true,
|
||||
default: ${assetFilename},
|
||||
ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
|
||||
return {
|
||||
$$typeof: Symbol.for('react.element'),
|
||||
type: 'svg',
|
||||
ref: ref,
|
||||
key: null,
|
||||
props: Object.assign({}, props, {
|
||||
children: ${assetFilename}
|
||||
})
|
||||
};
|
||||
}),
|
||||
};`;
|
||||
}
|
||||
|
||||
return `module.exports = ${assetFilename};`;
|
||||
},
|
||||
};
|
||||
@@ -1,141 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const paths = require('./paths');
|
||||
const chalk = require('react-dev-utils/chalk');
|
||||
const resolve = require('resolve');
|
||||
|
||||
/**
|
||||
* Get additional module paths based on the baseUrl of a compilerOptions object.
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
function getAdditionalModulePaths(options = {}) {
|
||||
const baseUrl = options.baseUrl;
|
||||
|
||||
// We need to explicitly check for null and undefined (and not a falsy value) because
|
||||
// TypeScript treats an empty string as `.`.
|
||||
if (baseUrl == null) {
|
||||
// If there's no baseUrl set we respect NODE_PATH
|
||||
// Note that NODE_PATH is deprecated and will be removed
|
||||
// in the next major release of create-react-app.
|
||||
|
||||
const nodePath = process.env.NODE_PATH || '';
|
||||
return nodePath.split(path.delimiter).filter(Boolean);
|
||||
}
|
||||
|
||||
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
|
||||
|
||||
// We don't need to do anything if `baseUrl` is set to `node_modules`. This is
|
||||
// the default behavior.
|
||||
if (path.relative(paths.appNodeModules, baseUrlResolved) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Allow the user set the `baseUrl` to `appSrc`.
|
||||
if (path.relative(paths.appSrc, baseUrlResolved) === '') {
|
||||
return [paths.appSrc];
|
||||
}
|
||||
|
||||
// If the path is equal to the root directory we ignore it here.
|
||||
// We don't want to allow importing from the root directly as source files are
|
||||
// not transpiled outside of `src`. We do allow importing them with the
|
||||
// absolute path (e.g. `src/Components/Button.js`) but we set that up with
|
||||
// an alias.
|
||||
if (path.relative(paths.appPath, baseUrlResolved) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise, throw an error.
|
||||
throw new Error(
|
||||
chalk.red.bold(
|
||||
"Your project's `baseUrl` can only be set to `src` or `node_modules`." +
|
||||
' Create React App does not support other values at this time.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get webpack aliases based on the baseUrl of a compilerOptions object.
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
function getWebpackAliases(options = {}) {
|
||||
const baseUrl = options.baseUrl;
|
||||
|
||||
if (!baseUrl) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
|
||||
|
||||
if (path.relative(paths.appPath, baseUrlResolved) === '') {
|
||||
return {
|
||||
src: paths.appSrc,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get jest aliases based on the baseUrl of a compilerOptions object.
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
function getJestAliases(options = {}) {
|
||||
const baseUrl = options.baseUrl;
|
||||
|
||||
if (!baseUrl) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
|
||||
|
||||
if (path.relative(paths.appPath, baseUrlResolved) === '') {
|
||||
return {
|
||||
'^src/(.*)$': '<rootDir>/src/$1',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getModules() {
|
||||
// Check if TypeScript is setup
|
||||
const hasTsConfig = fs.existsSync(paths.appTsConfig);
|
||||
const hasJsConfig = fs.existsSync(paths.appJsConfig);
|
||||
|
||||
if (hasTsConfig && hasJsConfig) {
|
||||
throw new Error(
|
||||
'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.'
|
||||
);
|
||||
}
|
||||
|
||||
let config;
|
||||
|
||||
// If there's a tsconfig.json we assume it's a
|
||||
// TypeScript project and set up the config
|
||||
// based on tsconfig.json
|
||||
if (hasTsConfig) {
|
||||
const ts = require(resolve.sync('typescript', {
|
||||
basedir: paths.appNodeModules,
|
||||
}));
|
||||
config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config;
|
||||
// Otherwise we'll check if there is jsconfig.json
|
||||
// for non TS projects.
|
||||
} else if (hasJsConfig) {
|
||||
config = require(paths.appJsConfig);
|
||||
}
|
||||
|
||||
config = config || {};
|
||||
const options = config.compilerOptions || {};
|
||||
|
||||
const additionalModulePaths = getAdditionalModulePaths(options);
|
||||
|
||||
return {
|
||||
additionalModulePaths: additionalModulePaths,
|
||||
webpackAliases: getWebpackAliases(options),
|
||||
jestAliases: getJestAliases(options),
|
||||
hasTsConfig,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = getModules();
|
||||
@@ -1,72 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath');
|
||||
|
||||
// Make sure any symlinks in the project folder are resolved:
|
||||
// https://github.com/facebook/create-react-app/issues/637
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
|
||||
|
||||
// We use `PUBLIC_URL` environment variable or "homepage" field to infer
|
||||
// "public path" at which the app is served.
|
||||
// webpack needs to know it to put the right <script> hrefs into HTML even in
|
||||
// single-page apps that may serve index.html for nested URLs like /todos/42.
|
||||
// We can't use a relative path in HTML because we don't want to load something
|
||||
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
|
||||
const publicUrlOrPath = getPublicUrlOrPath(
|
||||
process.env.NODE_ENV === 'development',
|
||||
require(resolveApp('package.json')).homepage,
|
||||
process.env.PUBLIC_URL
|
||||
);
|
||||
|
||||
const moduleFileExtensions = [
|
||||
'web.mjs',
|
||||
'mjs',
|
||||
'web.js',
|
||||
'js',
|
||||
'web.ts',
|
||||
'ts',
|
||||
'web.tsx',
|
||||
'tsx',
|
||||
'json',
|
||||
'web.jsx',
|
||||
'jsx',
|
||||
];
|
||||
|
||||
// Resolve file paths in the same order as webpack
|
||||
const resolveModule = (resolveFn, filePath) => {
|
||||
const extension = moduleFileExtensions.find(extension =>
|
||||
fs.existsSync(resolveFn(`${filePath}.${extension}`))
|
||||
);
|
||||
|
||||
if (extension) {
|
||||
return resolveFn(`${filePath}.${extension}`);
|
||||
}
|
||||
|
||||
return resolveFn(`${filePath}.js`);
|
||||
};
|
||||
|
||||
// config after eject: we're in ./config/
|
||||
module.exports = {
|
||||
dotenv: resolveApp('.env'),
|
||||
appPath: resolveApp('.'),
|
||||
appBuild: resolveApp('build'),
|
||||
appPublic: resolveApp('public'),
|
||||
appHtml: resolveApp('public/index.html'),
|
||||
appIndexJs: resolveModule(resolveApp, 'src/index'),
|
||||
appPackageJson: resolveApp('package.json'),
|
||||
appSrc: resolveApp('src'),
|
||||
appTsConfig: resolveApp('tsconfig.json'),
|
||||
appJsConfig: resolveApp('jsconfig.json'),
|
||||
yarnLockFile: resolveApp('yarn.lock'),
|
||||
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
|
||||
proxySetup: resolveApp('src/setupProxy.js'),
|
||||
appNodeModules: resolveApp('node_modules'),
|
||||
publicUrlOrPath,
|
||||
};
|
||||
|
||||
|
||||
|
||||
module.exports.moduleFileExtensions = moduleFileExtensions;
|
||||
@@ -1,35 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const { resolveModuleName } = require('ts-pnp');
|
||||
|
||||
exports.resolveModuleName = (
|
||||
typescript,
|
||||
moduleName,
|
||||
containingFile,
|
||||
compilerOptions,
|
||||
resolutionHost
|
||||
) => {
|
||||
return resolveModuleName(
|
||||
moduleName,
|
||||
containingFile,
|
||||
compilerOptions,
|
||||
resolutionHost,
|
||||
typescript.resolveModuleName
|
||||
);
|
||||
};
|
||||
|
||||
exports.resolveTypeReferenceDirective = (
|
||||
typescript,
|
||||
moduleName,
|
||||
containingFile,
|
||||
compilerOptions,
|
||||
resolutionHost
|
||||
) => {
|
||||
return resolveModuleName(
|
||||
moduleName,
|
||||
containingFile,
|
||||
compilerOptions,
|
||||
resolutionHost,
|
||||
typescript.resolveTypeReferenceDirective
|
||||
);
|
||||
};
|
||||
@@ -1,691 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const resolve = require('resolve');
|
||||
const PnpWebpackPlugin = require('pnp-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
|
||||
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const safePostCssParser = require('postcss-safe-parser');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
|
||||
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
|
||||
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
|
||||
const paths = require('./paths');
|
||||
const modules = require('./modules');
|
||||
const getClientEnvironment = require('./env');
|
||||
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
|
||||
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
|
||||
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
|
||||
const CompressionPlugin = require("compression-webpack-plugin");
|
||||
|
||||
const postcssNormalize = require('postcss-normalize');
|
||||
const { postcssRTLCSS} = require('postcss-rtlcss');
|
||||
|
||||
|
||||
const appPackageJson = require(paths.appPackageJson);
|
||||
|
||||
// Source maps are resource heavy and can cause out of memory issue for large source files.
|
||||
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
|
||||
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
|
||||
// makes for a smoother build process.
|
||||
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
|
||||
|
||||
const imageInlineSizeLimit = parseInt(
|
||||
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
|
||||
);
|
||||
|
||||
// Check if TypeScript is setup
|
||||
const useTypeScript = fs.existsSync(paths.appTsConfig);
|
||||
|
||||
// style files regexes
|
||||
const cssRegex = /\.css$/;
|
||||
const cssModuleRegex = /\.module\.css$/;
|
||||
const sassRegex = /\.(scss|sass)$/;
|
||||
const sassModuleRegex = /\.module\.(scss|sass)$/;
|
||||
|
||||
// This is the production and development configuration.
|
||||
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
|
||||
module.exports = function(webpackEnv) {
|
||||
const isEnvDevelopment = webpackEnv === 'development';
|
||||
const isEnvProduction = webpackEnv === 'production';
|
||||
|
||||
// Variable used for enabling profiling in Production
|
||||
// passed into alias object. Uses a flag if passed into the build command
|
||||
const isEnvProductionProfile =
|
||||
isEnvProduction && process.argv.includes('--profile');
|
||||
|
||||
// We will provide `paths.publicUrlOrPath` to our app
|
||||
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
|
||||
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
|
||||
// Get environment variables to inject into our app.
|
||||
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
|
||||
|
||||
// common function to get style loaders
|
||||
const getStyleLoaders = (cssOptions, preProcessor) => {
|
||||
const loaders = [
|
||||
isEnvDevelopment && require.resolve('style-loader'),
|
||||
isEnvProduction && {
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
// css is located in `static/css`, use '../../' to locate index.html folder
|
||||
// in production `paths.publicUrlOrPath` can be a relative path
|
||||
options: paths.publicUrlOrPath.startsWith('.')
|
||||
? { publicPath: '../../' }
|
||||
: {},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
options: cssOptions,
|
||||
},
|
||||
{
|
||||
// Options for PostCSS as we reference these options twice
|
||||
// Adds vendor prefixing based on your specified browser support in
|
||||
// package.json
|
||||
loader: require.resolve('postcss-loader'),
|
||||
options: {
|
||||
// Necessary for external CSS imports to work
|
||||
// https://github.com/facebook/create-react-app/issues/2677
|
||||
ident: 'postcss',
|
||||
plugins: () => [
|
||||
// Postcss rtlcss plugin.
|
||||
// require( 'postcss-rtl' )({
|
||||
// // options here.
|
||||
// removeComments: false,
|
||||
// }),
|
||||
postcssRTLCSS({
|
||||
|
||||
}),
|
||||
require('postcss-flexbugs-fixes'),
|
||||
require('postcss-preset-env')({
|
||||
autoprefixer: {
|
||||
flexbox: 'no-2009',
|
||||
},
|
||||
stage: 3,
|
||||
}),
|
||||
// Adds PostCSS Normalize as the reset css with default options,
|
||||
// so that it honors browserslist config in package.json
|
||||
// which in turn let's users customize the target behavior as per their needs.
|
||||
postcssNormalize(),
|
||||
],
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
},
|
||||
},
|
||||
].filter(Boolean);
|
||||
if (preProcessor) {
|
||||
loaders.push(
|
||||
{
|
||||
loader: require.resolve('resolve-url-loader'),
|
||||
options: {
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve(preProcessor),
|
||||
options: {
|
||||
sourceMap: true,
|
||||
sassOptions: {
|
||||
sourceComments: true,
|
||||
outputStyle: 'expanded'
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
return loaders;
|
||||
};
|
||||
|
||||
return {
|
||||
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
|
||||
// Stop compilation early in production
|
||||
bail: isEnvProduction,
|
||||
devtool: isEnvProduction
|
||||
? shouldUseSourceMap
|
||||
? 'source-map'
|
||||
: false
|
||||
: isEnvDevelopment && 'cheap-module-source-map',
|
||||
// These are the "entry points" to our application.
|
||||
// This means they will be the "root" imports that are included in JS bundle.
|
||||
entry: [
|
||||
// Include an alternative client for WebpackDevServer. A client's job is to
|
||||
// connect to WebpackDevServer by a socket and get notified about changes.
|
||||
// When you save a file, the client will either apply hot updates (in case
|
||||
// of CSS changes), or refresh the page (in case of JS changes). When you
|
||||
// make a syntax error, this client will display a syntax error overlay.
|
||||
// Note: instead of the default WebpackDevServer client, we use a custom one
|
||||
// to bring better experience for Create React App users. You can replace
|
||||
// the line below with these two lines if you prefer the stock client:
|
||||
// require.resolve('webpack-dev-server/client') + '?/',
|
||||
// require.resolve('webpack/hot/dev-server'),
|
||||
isEnvDevelopment &&
|
||||
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||
// Finally, this is your app's code:
|
||||
paths.appIndexJs,
|
||||
// We include the app code last so that if there is a runtime error during
|
||||
// initialization, it doesn't blow up the WebpackDevServer client, and
|
||||
// changing JS code would still trigger a refresh.
|
||||
].filter(Boolean),
|
||||
output: {
|
||||
// The build folder.
|
||||
path: isEnvProduction ? paths.appBuild : undefined,
|
||||
// Add /* filename */ comments to generated require()s in the output.
|
||||
pathinfo: isEnvDevelopment,
|
||||
// There will be one main bundle, and one file per asynchronous chunk.
|
||||
// In development, it does not produce real files.
|
||||
filename: isEnvProduction
|
||||
? 'static/js/[name].[contenthash:8].js'
|
||||
: isEnvDevelopment && 'static/js/bundle.js',
|
||||
// TODO: remove this when upgrading to webpack 5
|
||||
futureEmitAssets: true,
|
||||
// There are also additional JS chunk files if you use code splitting.
|
||||
chunkFilename: isEnvProduction
|
||||
? 'static/js/[name].[contenthash:8].chunk.js'
|
||||
: isEnvDevelopment && 'static/js/[name].chunk.js',
|
||||
// webpack uses `publicPath` to determine where the app is being served from.
|
||||
// It requires a trailing slash, or the file assets will get an incorrect path.
|
||||
// We inferred the "public path" (such as / or /my-project) from homepage.
|
||||
publicPath: paths.publicUrlOrPath,
|
||||
// Point sourcemap entries to original disk location (format as URL on Windows)
|
||||
devtoolModuleFilenameTemplate: isEnvProduction
|
||||
? info =>
|
||||
path
|
||||
.relative(paths.appSrc, info.absoluteResourcePath)
|
||||
.replace(/\\/g, '/')
|
||||
: isEnvDevelopment &&
|
||||
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
|
||||
// Prevents conflicts when multiple webpack runtimes (from different apps)
|
||||
// are used on the same page.
|
||||
jsonpFunction: `webpackJsonp${appPackageJson.name}`,
|
||||
// this defaults to 'window', but by setting it to 'this' then
|
||||
// module chunks which are built will work in web workers as well.
|
||||
globalObject: 'this',
|
||||
},
|
||||
optimization: {
|
||||
minimize: isEnvProduction,
|
||||
minimizer: [
|
||||
// This is only used in production mode
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
parse: {
|
||||
// We want terser to parse ecma 8 code. However, we don't want it
|
||||
// to apply any minification steps that turns valid ecma 5 code
|
||||
// into invalid ecma 5 code. This is why the 'compress' and 'output'
|
||||
// sections only apply transformations that are ecma 5 safe
|
||||
// https://github.com/facebook/create-react-app/pull/4234
|
||||
ecma: 8,
|
||||
},
|
||||
compress: {
|
||||
ecma: 5,
|
||||
warnings: false,
|
||||
// Disabled because of an issue with Uglify breaking seemingly valid code:
|
||||
// https://github.com/facebook/create-react-app/issues/2376
|
||||
// Pending further investigation:
|
||||
// https://github.com/mishoo/UglifyJS2/issues/2011
|
||||
comparisons: false,
|
||||
// Disabled because of an issue with Terser breaking valid code:
|
||||
// https://github.com/facebook/create-react-app/issues/5250
|
||||
// Pending further investigation:
|
||||
// https://github.com/terser-js/terser/issues/120
|
||||
inline: 2,
|
||||
},
|
||||
mangle: {
|
||||
safari10: true,
|
||||
},
|
||||
// Added for profiling in devtools
|
||||
keep_classnames: isEnvProductionProfile,
|
||||
keep_fnames: isEnvProductionProfile,
|
||||
output: {
|
||||
ecma: 5,
|
||||
comments: false,
|
||||
// Turned on because emoji and regex is not minified properly using default
|
||||
// https://github.com/facebook/create-react-app/issues/2488
|
||||
ascii_only: true,
|
||||
},
|
||||
},
|
||||
sourceMap: shouldUseSourceMap,
|
||||
}),
|
||||
// This is only used in production mode
|
||||
new OptimizeCSSAssetsPlugin({
|
||||
cssProcessorOptions: {
|
||||
parser: safePostCssParser,
|
||||
map: shouldUseSourceMap
|
||||
? {
|
||||
// `inline: false` forces the sourcemap to be output into a
|
||||
// separate file
|
||||
inline: false,
|
||||
// `annotation: true` appends the sourceMappingURL to the end of
|
||||
// the css file, helping the browser find the sourcemap
|
||||
annotation: true,
|
||||
}
|
||||
: false,
|
||||
},
|
||||
cssProcessorPluginOptions: {
|
||||
preset: ['default', { minifyFontValues: { removeQuotes: false } }],
|
||||
},
|
||||
}),
|
||||
],
|
||||
// Automatically split vendor and commons
|
||||
// https://twitter.com/wSokra/status/969633336732905474
|
||||
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
name: false,
|
||||
},
|
||||
// Keep the runtime chunk separated to enable long term caching
|
||||
// https://twitter.com/wSokra/status/969679223278505985
|
||||
// https://github.com/facebook/create-react-app/issues/5358
|
||||
runtimeChunk: {
|
||||
name: entrypoint => `runtime-${entrypoint.name}`,
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
// This allows you to set a fallback for where webpack should look for modules.
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebook/create-react-app/issues/253
|
||||
modules: ['node_modules', paths.appNodeModules].concat(
|
||||
modules.additionalModulePaths || []
|
||||
),
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
// https://github.com/facebook/create-react-app/issues/290
|
||||
// `web` extension prefixes have been added for better support
|
||||
// for React Native Web.
|
||||
extensions: paths.moduleFileExtensions
|
||||
.map(ext => `.${ext}`)
|
||||
.filter(ext => useTypeScript || !ext.includes('ts')),
|
||||
alias: {
|
||||
// Support React Native Web
|
||||
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
|
||||
'react-native': 'react-native-web',
|
||||
// Allows for better profiling with ReactDevTools
|
||||
...(isEnvProductionProfile && {
|
||||
'react-dom$': 'react-dom/profiling',
|
||||
'scheduler/tracing': 'scheduler/tracing-profiling',
|
||||
}),
|
||||
...(modules.webpackAliases || {}),
|
||||
},
|
||||
plugins: [
|
||||
// Adds support for installing with Plug'n'Play, leading to faster installs and adding
|
||||
// guards against forgotten dependencies and such.
|
||||
PnpWebpackPlugin,
|
||||
// Prevents users from importing files from outside of src/ (or node_modules/).
|
||||
// This often causes confusion because we only process files within src/ with babel.
|
||||
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
|
||||
// please link the files into your node_modules/ and let module-resolution kick in.
|
||||
// Make sure your source files are compiled, as they will not be processed in any way.
|
||||
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
|
||||
],
|
||||
},
|
||||
resolveLoader: {
|
||||
plugins: [
|
||||
// Also related to Plug'n'Play, but this time it tells webpack to load its loaders
|
||||
// from the current package.
|
||||
PnpWebpackPlugin.moduleLoader(module),
|
||||
],
|
||||
},
|
||||
module: {
|
||||
strictExportPresence: true,
|
||||
rules: [
|
||||
// Disable require.ensure as it's not a standard language feature.
|
||||
{ parser: { requireEnsure: false } },
|
||||
|
||||
// First, run the linter.
|
||||
// It's important to do this before Babel processes the JS.
|
||||
{
|
||||
test: /\.(js|mjs|jsx|ts|tsx)$/,
|
||||
enforce: 'pre',
|
||||
use: [
|
||||
{
|
||||
options: {
|
||||
cache: true,
|
||||
formatter: require.resolve('react-dev-utils/eslintFormatter'),
|
||||
eslintPath: require.resolve('eslint'),
|
||||
resolvePluginsRelativeTo: __dirname,
|
||||
|
||||
},
|
||||
loader: require.resolve('eslint-loader'),
|
||||
},
|
||||
],
|
||||
include: paths.appSrc,
|
||||
},
|
||||
{
|
||||
// "oneOf" will traverse all following loaders until one will
|
||||
// match the requirements. When no loader matches it will fall
|
||||
// back to the "file" loader at the end of the loader list.
|
||||
oneOf: [
|
||||
// "url" loader works like "file" loader except that it embeds assets
|
||||
// smaller than specified limit in bytes as data URLs to avoid requests.
|
||||
// A missing `test` is equivalent to a match.
|
||||
{
|
||||
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
loader: require.resolve('url-loader'),
|
||||
options: {
|
||||
limit: imageInlineSizeLimit,
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// Process application JS with Babel.
|
||||
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
|
||||
{
|
||||
test: /\.(js|mjs|jsx|ts|tsx)$/,
|
||||
include: paths.appSrc,
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: {
|
||||
customize: require.resolve(
|
||||
'babel-preset-react-app/webpack-overrides'
|
||||
),
|
||||
|
||||
plugins: [
|
||||
[
|
||||
require.resolve('babel-plugin-named-asset-import'),
|
||||
{
|
||||
loaderMap: {
|
||||
svg: {
|
||||
ReactComponent:
|
||||
'@svgr/webpack?-svgo,+titleProp,+ref![path]',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
// It enables caching results in ./node_modules/.cache/babel-loader/
|
||||
// directory for faster rebuilds.
|
||||
cacheDirectory: true,
|
||||
// See #6846 for context on why cacheCompression is disabled
|
||||
cacheCompression: false,
|
||||
compact: isEnvProduction,
|
||||
},
|
||||
},
|
||||
// Process any JS outside of the app with Babel.
|
||||
// Unlike the application JS, we only compile the standard ES features.
|
||||
{
|
||||
test: /\.(js|mjs)$/,
|
||||
exclude: /@babel(?:\/|\\{1,2})runtime/,
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: {
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
compact: false,
|
||||
presets: [
|
||||
[
|
||||
require.resolve('babel-preset-react-app/dependencies'),
|
||||
{ helpers: true },
|
||||
],
|
||||
],
|
||||
cacheDirectory: true,
|
||||
// See #6846 for context on why cacheCompression is disabled
|
||||
cacheCompression: false,
|
||||
|
||||
// Babel sourcemaps are needed for debugging into node_modules
|
||||
// code. Without the options below, debuggers like VSCode
|
||||
// show incorrect code and set breakpoints on the wrong lines.
|
||||
sourceMaps: shouldUseSourceMap,
|
||||
inputSourceMap: shouldUseSourceMap,
|
||||
},
|
||||
},
|
||||
// "postcss" loader applies autoprefixer to our CSS.
|
||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||
// "style" loader turns CSS into JS modules that inject <style> tags.
|
||||
// In production, we use MiniCSSExtractPlugin to extract that CSS
|
||||
// to a file, but in development "style" loader enables hot editing
|
||||
// of CSS.
|
||||
// By default we support CSS Modules with the extension .module.css
|
||||
{
|
||||
test: cssRegex,
|
||||
exclude: cssModuleRegex,
|
||||
use: getStyleLoaders({
|
||||
importLoaders: 1,
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
}),
|
||||
// Don't consider CSS imports dead code even if the
|
||||
// containing package claims to have no side effects.
|
||||
// Remove this when webpack adds a warning or an error for this.
|
||||
// See https://github.com/webpack/webpack/issues/6571
|
||||
sideEffects: true,
|
||||
},
|
||||
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
|
||||
// using the extension .module.css
|
||||
{
|
||||
test: cssModuleRegex,
|
||||
use: getStyleLoaders({
|
||||
importLoaders: 1,
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
modules: {
|
||||
getLocalIdent: getCSSModuleLocalIdent,
|
||||
},
|
||||
}),
|
||||
},
|
||||
// Opt-in support for SASS (using .scss or .sass extensions).
|
||||
// By default we support SASS Modules with the
|
||||
// extensions .module.scss or .module.sass
|
||||
{
|
||||
test: sassRegex,
|
||||
exclude: sassModuleRegex,
|
||||
use: getStyleLoaders(
|
||||
{
|
||||
importLoaders: 3,
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
},
|
||||
'sass-loader'
|
||||
),
|
||||
// Don't consider CSS imports dead code even if the
|
||||
// containing package claims to have no side effects.
|
||||
// Remove this when webpack adds a warning or an error for this.
|
||||
// See https://github.com/webpack/webpack/issues/6571
|
||||
sideEffects: true,
|
||||
},
|
||||
// Adds support for CSS Modules, but using SASS
|
||||
// using the extension .module.scss or .module.sass
|
||||
{
|
||||
test: sassModuleRegex,
|
||||
use: getStyleLoaders(
|
||||
{
|
||||
importLoaders: 3,
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
modules: {
|
||||
getLocalIdent: getCSSModuleLocalIdent,
|
||||
},
|
||||
},
|
||||
'sass-loader'
|
||||
),
|
||||
},
|
||||
// "file" loader makes sure those assets get served by WebpackDevServer.
|
||||
// When you `import` an asset, you get its (virtual) filename.
|
||||
// In production, they would get copied to the `build` folder.
|
||||
// This loader doesn't use a "test" so it will catch all modules
|
||||
// that fall through the other loaders.
|
||||
{
|
||||
loader: require.resolve('file-loader'),
|
||||
// Exclude `js` files to keep "css" loader working as it injects
|
||||
// its runtime that would otherwise be processed through "file" loader.
|
||||
// Also exclude `html` and `json` extensions so they get processed
|
||||
// by webpacks internal loaders.
|
||||
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
|
||||
options: {
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// ** STOP ** Are you adding a new loader?
|
||||
// Make sure to add the new loader(s) before the "file" loader.
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin(
|
||||
Object.assign(
|
||||
{},
|
||||
{
|
||||
inject: true,
|
||||
template: paths.appHtml,
|
||||
},
|
||||
isEnvProduction
|
||||
? {
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true,
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
),
|
||||
// Inlines the webpack runtime script. This script is too small to warrant
|
||||
// a network request.
|
||||
// https://github.com/facebook/create-react-app/issues/5358
|
||||
isEnvProduction &&
|
||||
shouldInlineRuntimeChunk &&
|
||||
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
|
||||
// Makes some environment variables available in index.html.
|
||||
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// It will be an empty string unless you specify "homepage"
|
||||
// in `package.json`, in which case it will be the pathname of that URL.
|
||||
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
|
||||
// This gives some necessary context to module not found errors, such as
|
||||
// the requesting resource.
|
||||
new ModuleNotFoundPlugin(paths.appPath),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
|
||||
// It is absolutely essential that NODE_ENV is set to production
|
||||
// during a production build.
|
||||
// Otherwise React will be compiled in the very slow development mode.
|
||||
new webpack.DefinePlugin(env.stringified),
|
||||
// This is necessary to emit hot updates (currently CSS only):
|
||||
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
|
||||
// Watcher doesn't work well if you mistype casing in a path so we use
|
||||
// a plugin that prints an error when you attempt to do this.
|
||||
// See https://github.com/facebook/create-react-app/issues/240
|
||||
isEnvDevelopment && new CaseSensitivePathsPlugin(),
|
||||
// If you require a missing module and then `npm install` it, you still have
|
||||
// to restart the development server for webpack to discover it. This plugin
|
||||
// makes the discovery automatic so you don't have to restart.
|
||||
// See https://github.com/facebook/create-react-app/issues/186
|
||||
isEnvDevelopment &&
|
||||
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
|
||||
isEnvProduction &&
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
// both options are optional
|
||||
filename: 'static/css/[name].[contenthash:8].css',
|
||||
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
|
||||
}),
|
||||
// Generate an asset manifest file with the following content:
|
||||
// - "files" key: Mapping of all asset filenames to their corresponding
|
||||
// output file so that tools can pick it up without having to parse
|
||||
// `index.html`
|
||||
// - "entrypoints" key: Array of files which are included in `index.html`,
|
||||
// can be used to reconstruct the HTML if necessary
|
||||
new ManifestPlugin({
|
||||
fileName: 'asset-manifest.json',
|
||||
publicPath: paths.publicUrlOrPath,
|
||||
generate: (seed, files, entrypoints) => {
|
||||
const manifestFiles = files.reduce((manifest, file) => {
|
||||
manifest[file.name] = file.path;
|
||||
return manifest;
|
||||
}, seed);
|
||||
const entrypointFiles = entrypoints.main.filter(
|
||||
fileName => !fileName.endsWith('.map')
|
||||
);
|
||||
|
||||
return {
|
||||
files: manifestFiles,
|
||||
entrypoints: entrypointFiles,
|
||||
};
|
||||
},
|
||||
}),
|
||||
// Moment.js is an extremely popular library that bundles large locale files
|
||||
// by default due to how webpack interprets its code. This is a practical
|
||||
// solution that requires the user to opt into importing specific locales.
|
||||
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
|
||||
// You can remove this if you don't use Moment.js:
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
// Generate a service worker script that will precache, and keep up to date,
|
||||
// the HTML & assets that are part of the webpack build.
|
||||
isEnvProduction &&
|
||||
new WorkboxWebpackPlugin.GenerateSW({
|
||||
clientsClaim: true,
|
||||
exclude: [/\.map$/, /asset-manifest\.json$/],
|
||||
importWorkboxFrom: 'cdn',
|
||||
navigateFallback: paths.publicUrlOrPath + 'index.html',
|
||||
navigateFallbackBlacklist: [
|
||||
// Exclude URLs starting with /_, as they're likely an API call
|
||||
new RegExp('^/_'),
|
||||
// Exclude any URLs whose last part seems to be a file extension
|
||||
// as they're likely a resource and not a SPA route.
|
||||
// URLs containing a "?" character won't be blacklisted as they're likely
|
||||
// a route with query params (e.g. auth callbacks).
|
||||
new RegExp('/[^/?]+\\.[^/]+$'),
|
||||
],
|
||||
}),
|
||||
// TypeScript type checking
|
||||
useTypeScript &&
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
typescript: resolve.sync('typescript', {
|
||||
basedir: paths.appNodeModules,
|
||||
}),
|
||||
async: isEnvDevelopment,
|
||||
useTypescriptIncrementalApi: true,
|
||||
checkSyntacticErrors: true,
|
||||
resolveModuleNameModule: process.versions.pnp
|
||||
? `${__dirname}/pnpTs.js`
|
||||
: undefined,
|
||||
resolveTypeReferenceDirectiveModule: process.versions.pnp
|
||||
? `${__dirname}/pnpTs.js`
|
||||
: undefined,
|
||||
tsconfig: paths.appTsConfig,
|
||||
reportFiles: [
|
||||
'**',
|
||||
'!**/__tests__/**',
|
||||
'!**/?(*.)(spec|test).*',
|
||||
'!**/src/setupProxy.*',
|
||||
'!**/src/setupTests.*',
|
||||
],
|
||||
silent: true,
|
||||
// The formatter is invoked directly in WebpackDevServerUtils during development
|
||||
formatter: isEnvProduction ? typescriptFormatter : undefined,
|
||||
}),
|
||||
// Gzip compression.
|
||||
isEnvProduction &&
|
||||
new CompressionPlugin({
|
||||
// asset: "[path].gz[query]",
|
||||
algorithm: "gzip",
|
||||
test: /\.js$|\.css$|\.html$/,
|
||||
threshold: 10240,
|
||||
minRatio: 0.8
|
||||
})
|
||||
].filter(Boolean),
|
||||
// Some libraries import Node modules but don't use them in the browser.
|
||||
// Tell webpack to provide empty mocks for them so importing them works.
|
||||
node: {
|
||||
module: 'empty',
|
||||
dgram: 'empty',
|
||||
dns: 'mock',
|
||||
fs: 'empty',
|
||||
http2: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty',
|
||||
child_process: 'empty',
|
||||
},
|
||||
// Turn off performance processing because we utilize
|
||||
// our own hints via the FileSizeReporter
|
||||
performance: false,
|
||||
};
|
||||
};
|
||||
@@ -1,130 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
|
||||
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
|
||||
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
|
||||
const ignoredFiles = require('react-dev-utils/ignoredFiles');
|
||||
const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware');
|
||||
const paths = require('./paths');
|
||||
const getHttpsConfig = require('./getHttpsConfig');
|
||||
|
||||
const host = process.env.HOST || '0.0.0.0';
|
||||
const sockHost = process.env.WDS_SOCKET_HOST;
|
||||
const sockPath = process.env.WDS_SOCKET_PATH; // default: '/sockjs-node'
|
||||
const sockPort = process.env.WDS_SOCKET_PORT;
|
||||
|
||||
module.exports = function(proxy, allowedHost) {
|
||||
return {
|
||||
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
|
||||
// websites from potentially accessing local content through DNS rebinding:
|
||||
// https://github.com/webpack/webpack-dev-server/issues/887
|
||||
// https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
|
||||
// However, it made several existing use cases such as development in cloud
|
||||
// environment or subdomains in development significantly more complicated:
|
||||
// https://github.com/facebook/create-react-app/issues/2271
|
||||
// https://github.com/facebook/create-react-app/issues/2233
|
||||
// While we're investigating better solutions, for now we will take a
|
||||
// compromise. Since our WDS configuration only serves files in the `public`
|
||||
// folder we won't consider accessing them a vulnerability. However, if you
|
||||
// use the `proxy` feature, it gets more dangerous because it can expose
|
||||
// remote code execution vulnerabilities in backends like Django and Rails.
|
||||
// So we will disable the host check normally, but enable it if you have
|
||||
// specified the `proxy` setting. Finally, we let you override it if you
|
||||
// really know what you're doing with a special environment variable.
|
||||
disableHostCheck:
|
||||
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
|
||||
// Enable gzip compression of generated files.
|
||||
compress: true,
|
||||
// Silence WebpackDevServer's own logs since they're generally not useful.
|
||||
// It will still show compile warnings and errors with this setting.
|
||||
clientLogLevel: 'none',
|
||||
// By default WebpackDevServer serves physical files from current directory
|
||||
// in addition to all the virtual build products that it serves from memory.
|
||||
// This is confusing because those files won’t automatically be available in
|
||||
// production build folder unless we copy them. However, copying the whole
|
||||
// project directory is dangerous because we may expose sensitive files.
|
||||
// Instead, we establish a convention that only files in `public` directory
|
||||
// get served. Our build script will copy `public` into the `build` folder.
|
||||
// In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
|
||||
// <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
|
||||
// Note that we only recommend to use `public` folder as an escape hatch
|
||||
// for files like `favicon.ico`, `manifest.json`, and libraries that are
|
||||
// for some reason broken when imported through webpack. If you just want to
|
||||
// use an image, put it in `src` and `import` it from JavaScript instead.
|
||||
contentBase: paths.appPublic,
|
||||
contentBasePublicPath: paths.publicUrlOrPath,
|
||||
// By default files from `contentBase` will not trigger a page reload.
|
||||
watchContentBase: true,
|
||||
// Enable hot reloading server. It will provide WDS_SOCKET_PATH endpoint
|
||||
// for the WebpackDevServer client so it can learn when the files were
|
||||
// updated. The WebpackDevServer client is included as an entry point
|
||||
// in the webpack development configuration. Note that only changes
|
||||
// to CSS are currently hot reloaded. JS changes will refresh the browser.
|
||||
hot: true,
|
||||
// Use 'ws' instead of 'sockjs-node' on server since we're using native
|
||||
// websockets in `webpackHotDevClient`.
|
||||
transportMode: 'ws',
|
||||
// Prevent a WS client from getting injected as we're already including
|
||||
// `webpackHotDevClient`.
|
||||
injectClient: false,
|
||||
// Enable custom sockjs pathname for websocket connection to hot reloading server.
|
||||
// Enable custom sockjs hostname, pathname and port for websocket connection
|
||||
// to hot reloading server.
|
||||
sockHost,
|
||||
sockPath,
|
||||
sockPort,
|
||||
// It is important to tell WebpackDevServer to use the same "publicPath" path as
|
||||
// we specified in the webpack config. When homepage is '.', default to serving
|
||||
// from the root.
|
||||
// remove last slash so user can land on `/test` instead of `/test/`
|
||||
publicPath: paths.publicUrlOrPath.slice(0, -1),
|
||||
// WebpackDevServer is noisy by default so we emit custom message instead
|
||||
// by listening to the compiler events with `compiler.hooks[...].tap` calls above.
|
||||
quiet: true,
|
||||
// Reportedly, this avoids CPU overload on some systems.
|
||||
// https://github.com/facebook/create-react-app/issues/293
|
||||
// src/node_modules is not ignored to support absolute imports
|
||||
// https://github.com/facebook/create-react-app/issues/1065
|
||||
watchOptions: {
|
||||
ignored: ignoredFiles(paths.appSrc),
|
||||
},
|
||||
https: getHttpsConfig(),
|
||||
host,
|
||||
overlay: false,
|
||||
historyApiFallback: {
|
||||
// Paths with dots should still use the history fallback.
|
||||
// See https://github.com/facebook/create-react-app/issues/387.
|
||||
disableDotRule: true,
|
||||
index: paths.publicUrlOrPath,
|
||||
},
|
||||
public: allowedHost,
|
||||
// `proxy` is run between `before` and `after` `webpack-dev-server` hooks
|
||||
proxy,
|
||||
before(app, server) {
|
||||
// Keep `evalSourceMapMiddleware` and `errorOverlayMiddleware`
|
||||
// middlewares before `redirectServedPath` otherwise will not have any effect
|
||||
// This lets us fetch source contents from webpack for the error overlay
|
||||
app.use(evalSourceMapMiddleware(server));
|
||||
// This lets us open files from the runtime error overlay.
|
||||
app.use(errorOverlayMiddleware());
|
||||
|
||||
if (fs.existsSync(paths.proxySetup)) {
|
||||
// This registers user provided middleware for proxy reasons
|
||||
require(paths.proxySetup)(app);
|
||||
}
|
||||
},
|
||||
after(app) {
|
||||
// Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match
|
||||
app.use(redirectServedPath(paths.publicUrlOrPath));
|
||||
|
||||
// This service worker file is effectively a 'no-op' that will reset any
|
||||
// previous service worker registered for the same host:port combination.
|
||||
// We do this in development to avoid hitting the production cache if
|
||||
// it used the same host and port.
|
||||
// https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
|
||||
app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath));
|
||||
},
|
||||
};
|
||||
};
|
||||
9
craco.config.js
Normal file
9
craco.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
webpack: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
};
|
||||
13586
package-lock.json
generated
13586
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
164
package.json
164
package.json
@@ -3,8 +3,8 @@
|
||||
"version": "1.7.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7.8.4",
|
||||
"@blueprintjs-formik/core": "^0.2.1",
|
||||
"@blueprintjs-formik/datetime": "^0.3.4",
|
||||
"@blueprintjs-formik/select": "^0.1.4",
|
||||
"@blueprintjs/core": "^3.50.2",
|
||||
"@blueprintjs/datetime": "^3.23.12",
|
||||
@@ -14,69 +14,52 @@
|
||||
"@blueprintjs/timezone": "^3.6.2",
|
||||
"@casl/ability": "^5.4.3",
|
||||
"@casl/react": "^2.3.0",
|
||||
"@craco/craco": "^5.9.0",
|
||||
"@reduxjs/toolkit": "^1.2.5",
|
||||
"@sentry/react": "^6.13.2",
|
||||
"@sentry/tracing": "^6.13.2",
|
||||
"@svgr/webpack": "4.3.3",
|
||||
"@tanem/react-nprogress": "^3.0.24",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.4.0",
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/js-money": "^0.6.1",
|
||||
"@types/lodash": "^4.14.172",
|
||||
"@types/node": "^14.14.9",
|
||||
"@types/ramda": "^0.28.14",
|
||||
"@types/react": "^16.14.28",
|
||||
"@types/react-body-classname": "^1.1.7",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-transition-group": "^4.4.5",
|
||||
"@types/styled-components": "^5.1.25",
|
||||
"@types/yup": "^0.29.13",
|
||||
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
||||
"@typescript-eslint/parser": "^2.10.0",
|
||||
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
|
||||
"accounting": "^0.4.1",
|
||||
"axios": "^0.21.2",
|
||||
"babel-eslint": "10.0.3",
|
||||
"babel-jest": "^24.9.0",
|
||||
"babel-loader": "8.0.6",
|
||||
"babel-plugin-named-asset-import": "^0.3.6",
|
||||
"babel-preset-react-app": "^9.1.1",
|
||||
"basscss": "^8.0.2",
|
||||
"camelcase": "^5.3.1",
|
||||
"case-sensitive-paths-webpack-plugin": "2.3.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"css-loader": "3.4.2",
|
||||
"deep-map-keys": "^2.0.1",
|
||||
"deepdash": "^5.3.9",
|
||||
"dependency-graph": "^0.11.0",
|
||||
"dotenv": "8.2.0",
|
||||
"dotenv-expand": "5.1.0",
|
||||
"eslint": "^6.6.0",
|
||||
"eslint-config-react-app": "^5.2.0",
|
||||
"eslint-loader": "3.0.3",
|
||||
"eslint-plugin-flowtype": "4.6.0",
|
||||
"eslint-plugin-import": "2.20.0",
|
||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||
"eslint-plugin-react": "7.18.0",
|
||||
"eslint-plugin-react-hooks": "^1.6.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"file-loader": "4.3.0",
|
||||
"flow-bin": "^0.123.0",
|
||||
"formik": "^2.2.5",
|
||||
"fs-extra": "^8.1.0",
|
||||
"html-webpack-plugin": "4.0.0-beta.11",
|
||||
"identity-obj-proxy": "3.0.0",
|
||||
"http-proxy-middleware": "^1.0.0",
|
||||
"jest": "24.9.0",
|
||||
"jest-environment-jsdom-fourteen": "1.0.1",
|
||||
"jest-resolve": "24.9.0",
|
||||
"jest-watch-typeahead": "0.4.2",
|
||||
"js-money": "^0.6.3",
|
||||
"lodash": "^4.17.15",
|
||||
"mini-css-extract-plugin": "0.9.0",
|
||||
"moment": "^2.24.0",
|
||||
"moment-timezone": "^0.5.33",
|
||||
"node-sass": "^4.13.1",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.3",
|
||||
"pnp-webpack-plugin": "1.6.0",
|
||||
"postcss-flexbugs-fixes": "4.1.0",
|
||||
"postcss-loader": "3.0.0",
|
||||
"postcss-normalize": "8.0.1",
|
||||
"postcss-preset-env": "6.7.0",
|
||||
"postcss-rtl": "^1.7.3",
|
||||
"postcss-safe-parser": "4.0.1",
|
||||
"node-sass": "^4.14.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"query-string": "^7.1.1",
|
||||
"ramda": "^0.27.1",
|
||||
"react": "^16.12.0",
|
||||
"react": "^16.14.0",
|
||||
"react-app-polyfill": "^1.0.6",
|
||||
"react-body-classname": "^1.3.1",
|
||||
"react-content-loader": "^6.0.1",
|
||||
@@ -88,9 +71,11 @@
|
||||
"react-intl-universal": "^2.4.7",
|
||||
"react-loadable": "^5.5.0",
|
||||
"react-query": "^3.6.0",
|
||||
"react-query-devtools": "^2.1.1",
|
||||
"react-redux": "^7.1.3",
|
||||
"react-router-breadcrumbs-hoc": "^3.2.10",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom": "^5.3.3",
|
||||
"react-scripts": "^3.4.4",
|
||||
"react-scroll-sync": "^0.7.1",
|
||||
"react-scrollbars-custom": "^4.0.21",
|
||||
"react-sortablejs": "^2.0.11",
|
||||
@@ -102,34 +87,31 @@
|
||||
"react-use-context-menu": "^0.1.4",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"redux": "^4.0.5",
|
||||
"redux-devtools": "^3.5.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"resolve": "1.15.0",
|
||||
"resolve-url-loader": "3.1.1",
|
||||
"rtl-detect": "^1.0.3",
|
||||
"sass-loader": "8.0.2",
|
||||
"semver": "6.3.0",
|
||||
"style-loader": "0.23.1",
|
||||
"styled-components": "^5.3.1",
|
||||
"stylis-rtlcss": "^2.1.1",
|
||||
"terser-webpack-plugin": "2.3.4",
|
||||
"ts-pnp": "1.1.5",
|
||||
"url-loader": "2.3.0",
|
||||
"webpack": "4.41.5",
|
||||
"webpack-dev-server": "3.10.2",
|
||||
"webpack-manifest-plugin": "2.2.0",
|
||||
"workbox-webpack-plugin": "4.3.1",
|
||||
"typescript": "^4.8.3",
|
||||
"yup": "^0.28.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "PORT=8000 node scripts/start.js",
|
||||
"start-win": "cross-env PORT=8000 node scripts/start.js",
|
||||
"build": "node scripts/build.js",
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"test": "node scripts/test.js",
|
||||
"flow": "flow"
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"preinstall": "npx npm-force-resolutions"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
"proxy": "http://localhost:3000/",
|
||||
"devDependencies": {
|
||||
"@types/react-dom": "^16.9.16",
|
||||
"react-error-overlay": "^6.0.9"
|
||||
},
|
||||
"resolutions": {
|
||||
"react-error-overlay": "6.0.9"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -142,79 +124,5 @@
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-flow": "^7.9.0",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.9",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
"@types/yup": "^0.29.13",
|
||||
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
|
||||
"compression-webpack-plugin": "^6.1.0",
|
||||
"http-proxy-middleware": "^1.0.0",
|
||||
"postcss-rtlcss": "^1.7.2",
|
||||
"react-query-devtools": "^2.1.1",
|
||||
"redux-devtools": "^3.5.0",
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
"jest": {
|
||||
"roots": [
|
||||
"<rootDir>/src"
|
||||
],
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.{js,jsx,ts,tsx}",
|
||||
"!src/**/*.d.ts"
|
||||
],
|
||||
"setupFiles": [
|
||||
"react-app-polyfill/jsdom"
|
||||
],
|
||||
"setupFilesAfterEnv": [
|
||||
"<rootDir>/src/setupTests.js"
|
||||
],
|
||||
"testMatch": [
|
||||
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
|
||||
"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
|
||||
],
|
||||
"testEnvironment": "jest-environment-jsdom-fourteen",
|
||||
"transform": {
|
||||
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
|
||||
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
|
||||
"^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
|
||||
},
|
||||
"transformIgnorePatterns": [
|
||||
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
|
||||
"^.+\\.module\\.(css|sass|scss)$"
|
||||
],
|
||||
"modulePaths": [
|
||||
"/Users/ahmed/Documents/Ratteb/client/src"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^react-native$": "react-native-web",
|
||||
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
|
||||
},
|
||||
"moduleFileExtensions": [
|
||||
"web.js",
|
||||
"js",
|
||||
"web.ts",
|
||||
"ts",
|
||||
"web.tsx",
|
||||
"tsx",
|
||||
"json",
|
||||
"web.jsx",
|
||||
"jsx",
|
||||
"node"
|
||||
],
|
||||
"watchPlugins": [
|
||||
"jest-watch-typeahead/filename",
|
||||
"jest-watch-typeahead/testname"
|
||||
]
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"react-app"
|
||||
]
|
||||
},
|
||||
"proxy": "http://localhost:3000/"
|
||||
}
|
||||
}
|
||||
|
||||
211
scripts/build.js
211
scripts/build.js
@@ -1,211 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'production';
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
|
||||
const path = require('path');
|
||||
const chalk = require('react-dev-utils/chalk');
|
||||
const fs = require('fs-extra');
|
||||
const webpack = require('webpack');
|
||||
const configFactory = require('../config/webpack.config');
|
||||
const paths = require('../config/paths');
|
||||
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
|
||||
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
|
||||
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
|
||||
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
|
||||
const printBuildError = require('react-dev-utils/printBuildError');
|
||||
|
||||
const measureFileSizesBeforeBuild =
|
||||
FileSizeReporter.measureFileSizesBeforeBuild;
|
||||
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
|
||||
const useYarn = fs.existsSync(paths.yarnLockFile);
|
||||
|
||||
// These sizes are pretty large. We'll warn for bundles exceeding them.
|
||||
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
|
||||
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
|
||||
|
||||
const isInteractive = process.stdout.isTTY;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Generate configuration
|
||||
const config = configFactory('production');
|
||||
|
||||
// We require that you explicitly set browsers and do not fall back to
|
||||
// browserslist defaults.
|
||||
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
|
||||
checkBrowsers(paths.appPath, isInteractive)
|
||||
.then(() => {
|
||||
// First, read the current file sizes in build directory.
|
||||
// This lets us display how much they changed later.
|
||||
return measureFileSizesBeforeBuild(paths.appBuild);
|
||||
})
|
||||
.then(previousFileSizes => {
|
||||
// Remove all content but keep the directory so that
|
||||
// if you're in it, you don't end up in Trash
|
||||
fs.emptyDirSync(paths.appBuild);
|
||||
// Merge with the public folder
|
||||
copyPublicFolder();
|
||||
// Start the webpack build
|
||||
return build(previousFileSizes);
|
||||
})
|
||||
.then(
|
||||
({ stats, previousFileSizes, warnings }) => {
|
||||
if (warnings.length) {
|
||||
console.log(chalk.yellow('Compiled with warnings.\n'));
|
||||
console.log(warnings.join('\n\n'));
|
||||
console.log(
|
||||
'\nSearch for the ' +
|
||||
chalk.underline(chalk.yellow('keywords')) +
|
||||
' to learn more about each warning.'
|
||||
);
|
||||
console.log(
|
||||
'To ignore, add ' +
|
||||
chalk.cyan('// eslint-disable-next-line') +
|
||||
' to the line before.\n'
|
||||
);
|
||||
} else {
|
||||
console.log(chalk.green('Compiled successfully.\n'));
|
||||
}
|
||||
|
||||
console.log('File sizes after gzip:\n');
|
||||
printFileSizesAfterBuild(
|
||||
stats,
|
||||
previousFileSizes,
|
||||
paths.appBuild,
|
||||
WARN_AFTER_BUNDLE_GZIP_SIZE,
|
||||
WARN_AFTER_CHUNK_GZIP_SIZE
|
||||
);
|
||||
console.log();
|
||||
|
||||
const appPackage = require(paths.appPackageJson);
|
||||
const publicUrl = paths.publicUrlOrPath;
|
||||
const publicPath = config.output.publicPath;
|
||||
const buildFolder = path.relative(process.cwd(), paths.appBuild);
|
||||
printHostingInstructions(
|
||||
appPackage,
|
||||
publicUrl,
|
||||
publicPath,
|
||||
buildFolder,
|
||||
useYarn
|
||||
);
|
||||
},
|
||||
err => {
|
||||
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
|
||||
if (tscCompileOnError) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Compiled with the following type errors (you may want to check these before deploying your app):\n'
|
||||
)
|
||||
);
|
||||
printBuildError(err);
|
||||
} else {
|
||||
console.log(chalk.red('Failed to compile.\n'));
|
||||
printBuildError(err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
)
|
||||
.catch(err => {
|
||||
if (err && err.message) {
|
||||
console.log(err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Create the production build and print the deployment instructions.
|
||||
function build(previousFileSizes) {
|
||||
// We used to support resolving modules according to `NODE_PATH`.
|
||||
// This now has been deprecated in favor of jsconfig/tsconfig.json
|
||||
// This lets you use absolute paths in imports inside large monorepos:
|
||||
if (process.env.NODE_PATH) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
|
||||
)
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
console.log('Creating an optimized production build...');
|
||||
|
||||
const compiler = webpack(config);
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
let messages;
|
||||
if (err) {
|
||||
if (!err.message) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
let errMessage = err.message;
|
||||
|
||||
// Add additional information for postcss errors
|
||||
if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) {
|
||||
errMessage +=
|
||||
'\nCompileError: Begins at CSS selector ' +
|
||||
err['postcssNode'].selector;
|
||||
}
|
||||
|
||||
messages = formatWebpackMessages({
|
||||
errors: [errMessage],
|
||||
warnings: [],
|
||||
});
|
||||
} else {
|
||||
messages = formatWebpackMessages(
|
||||
stats.toJson({ all: false, warnings: true, errors: true })
|
||||
);
|
||||
}
|
||||
if (messages.errors.length) {
|
||||
// Only keep the first error. Others are often indicative
|
||||
// of the same problem, but confuse the reader with noise.
|
||||
if (messages.errors.length > 1) {
|
||||
messages.errors.length = 1;
|
||||
}
|
||||
return reject(new Error(messages.errors.join('\n\n')));
|
||||
}
|
||||
if (
|
||||
process.env.CI &&
|
||||
(typeof process.env.CI !== 'string' ||
|
||||
process.env.CI.toLowerCase() !== 'false') &&
|
||||
messages.warnings.length
|
||||
) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'\nTreating warnings as errors because process.env.CI = true.\n' +
|
||||
'Most CI servers set it automatically.\n'
|
||||
)
|
||||
);
|
||||
return reject(new Error(messages.warnings.join('\n\n')));
|
||||
}
|
||||
|
||||
return resolve({
|
||||
stats,
|
||||
previousFileSizes,
|
||||
warnings: messages.warnings,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function copyPublicFolder() {
|
||||
fs.copySync(paths.appPublic, paths.appBuild, {
|
||||
dereference: true,
|
||||
filter: file => file !== paths.appHtml,
|
||||
});
|
||||
}
|
||||
157
scripts/start.js
157
scripts/start.js
@@ -1,157 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'development';
|
||||
process.env.NODE_ENV = 'development';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
|
||||
const fs = require('fs');
|
||||
const chalk = require('react-dev-utils/chalk');
|
||||
const webpack = require('webpack');
|
||||
const WebpackDevServer = require('webpack-dev-server');
|
||||
const clearConsole = require('react-dev-utils/clearConsole');
|
||||
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
|
||||
const {
|
||||
choosePort,
|
||||
createCompiler,
|
||||
prepareProxy,
|
||||
prepareUrls,
|
||||
} = require('react-dev-utils/WebpackDevServerUtils');
|
||||
const openBrowser = require('react-dev-utils/openBrowser');
|
||||
const paths = require('../config/paths');
|
||||
const configFactory = require('../config/webpack.config');
|
||||
const createDevServerConfig = require('../config/webpackDevServer.config');
|
||||
|
||||
const useYarn = fs.existsSync(paths.yarnLockFile);
|
||||
const isInteractive = process.stdout.isTTY;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Tools like Cloud9 rely on this.
|
||||
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
|
||||
const HOST = process.env.HOST || '0.0.0.0';
|
||||
|
||||
if (process.env.HOST) {
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
`Attempting to bind to HOST environment variable: ${chalk.yellow(
|
||||
chalk.bold(process.env.HOST)
|
||||
)}`
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
|
||||
);
|
||||
console.log(
|
||||
`Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}`
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
// We require that you explicitly set browsers and do not fall back to
|
||||
// browserslist defaults.
|
||||
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
|
||||
checkBrowsers(paths.appPath, isInteractive)
|
||||
.then(() => {
|
||||
// We attempt to use the default port but if it is busy, we offer the user to
|
||||
// run on a different port. `choosePort()` Promise resolves to the next free port.
|
||||
return choosePort(HOST, DEFAULT_PORT);
|
||||
})
|
||||
.then(port => {
|
||||
if (port == null) {
|
||||
// We have not found a port.
|
||||
return;
|
||||
}
|
||||
|
||||
const config = configFactory('development');
|
||||
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
|
||||
const appName = require(paths.appPackageJson).name;
|
||||
const useTypeScript = fs.existsSync(paths.appTsConfig);
|
||||
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
|
||||
const urls = prepareUrls(
|
||||
protocol,
|
||||
HOST,
|
||||
port,
|
||||
paths.publicUrlOrPath.slice(0, -1)
|
||||
);
|
||||
const devSocket = {
|
||||
warnings: warnings =>
|
||||
devServer.sockWrite(devServer.sockets, 'warnings', warnings),
|
||||
errors: errors =>
|
||||
devServer.sockWrite(devServer.sockets, 'errors', errors),
|
||||
};
|
||||
// Create a webpack compiler that is configured with custom messages.
|
||||
const compiler = createCompiler({
|
||||
appName,
|
||||
config,
|
||||
devSocket,
|
||||
urls,
|
||||
useYarn,
|
||||
useTypeScript,
|
||||
tscCompileOnError,
|
||||
webpack,
|
||||
});
|
||||
// Load proxy config
|
||||
const proxySetting = require(paths.appPackageJson).proxy;
|
||||
const proxyConfig = prepareProxy(
|
||||
proxySetting,
|
||||
paths.appPublic,
|
||||
paths.publicUrlOrPath
|
||||
);
|
||||
// Serve webpack assets generated by the compiler over a web server.
|
||||
const serverConfig = createDevServerConfig(
|
||||
proxyConfig,
|
||||
urls.lanUrlForConfig
|
||||
);
|
||||
const devServer = new WebpackDevServer(compiler, serverConfig);
|
||||
// Launch WebpackDevServer.
|
||||
devServer.listen(port, HOST, err => {
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
if (isInteractive) {
|
||||
clearConsole();
|
||||
}
|
||||
|
||||
// We used to support resolving modules according to `NODE_PATH`.
|
||||
// This now has been deprecated in favor of jsconfig/tsconfig.json
|
||||
// This lets you use absolute paths in imports inside large monorepos:
|
||||
if (process.env.NODE_PATH) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
|
||||
)
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
console.log(chalk.cyan('Starting the development server...\n'));
|
||||
openBrowser(urls.localUrlForBrowser);
|
||||
});
|
||||
|
||||
['SIGINT', 'SIGTERM'].forEach(function(sig) {
|
||||
process.on(sig, function() {
|
||||
devServer.close();
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err.message) {
|
||||
console.log(err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'test';
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.PUBLIC_URL = '';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
|
||||
const jest = require('jest');
|
||||
const execSync = require('child_process').execSync;
|
||||
let argv = process.argv.slice(2);
|
||||
|
||||
function isInGitRepository() {
|
||||
try {
|
||||
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isInMercurialRepository() {
|
||||
try {
|
||||
execSync('hg --cwd . root', { stdio: 'ignore' });
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Watch unless on CI or explicitly running all tests
|
||||
if (
|
||||
!process.env.CI &&
|
||||
argv.indexOf('--watchAll') === -1 &&
|
||||
argv.indexOf('--watchAll=false') === -1
|
||||
) {
|
||||
// https://github.com/facebook/create-react-app/issues/5210
|
||||
const hasSourceControl = isInGitRepository() || isInMercurialRepository();
|
||||
argv.push(hasSourceControl ? '--watch' : '--watchAll');
|
||||
}
|
||||
|
||||
|
||||
jest.run(argv);
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
|
||||
export const TableStyle = {
|
||||
Constrant: 'constrant',
|
||||
Regular: 'regular'
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
export const AbilitySubject = {
|
||||
Item: 'Item',
|
||||
InventoryAdjustment: 'InventoryAdjustment',
|
||||
Estimate: 'SaleEstimate',
|
||||
Invoice: 'SaleInvoice',
|
||||
Receipt: 'SaleReceipt',
|
||||
PaymentReceive: 'PaymentReceive',
|
||||
Bill: 'Bill',
|
||||
PaymentMade: 'PaymentMade',
|
||||
Customer: 'Customer',
|
||||
Vendor: 'Vendor',
|
||||
Account: 'Account',
|
||||
ManualJournal: 'ManualJournal',
|
||||
Expense: 'Expense',
|
||||
Cashflow: 'Cashflow',
|
||||
Report: 'Report',
|
||||
Preferences: 'Preferences',
|
||||
ExchangeRate: 'ExchangeRate',
|
||||
SubscriptionBilling: 'SubscriptionBilling',
|
||||
CreditNote: 'CreditNote',
|
||||
VendorCredit: 'VendorCredit',
|
||||
};
|
||||
|
||||
export const ItemAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
};
|
||||
|
||||
export const InventoryAdjustmentAction = {
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
View: 'View',
|
||||
Delete: 'Delete',
|
||||
};
|
||||
|
||||
export const SaleEstimateAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
NotifyBySms: 'NotifyBySms',
|
||||
};
|
||||
|
||||
export const SaleInvoiceAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
Writeoff: 'bad-debt',
|
||||
NotifyBySms: 'NotifyBySms',
|
||||
};
|
||||
|
||||
export const SaleReceiptAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
NotifyBySms: 'NotifyBySms',
|
||||
};
|
||||
|
||||
export const PaymentReceiveAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
NotifyBySms: 'NotifyBySms',
|
||||
};
|
||||
|
||||
export const CreditNoteAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
Refund: 'Refund'
|
||||
};
|
||||
|
||||
export const VendorCreditAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
Refund: 'Refund'
|
||||
};
|
||||
export const BillAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
NotifyBySms: 'NotifyBySms',
|
||||
};
|
||||
|
||||
export const PaymentMadeAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
};
|
||||
|
||||
export const CustomerAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
};
|
||||
|
||||
export const VendorAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
};
|
||||
|
||||
export const AccountAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
TransactionsLocking: 'TransactionsLocking',
|
||||
};
|
||||
|
||||
export const ManualJournalAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
TransactionLocking: 'TransactionLocking',
|
||||
};
|
||||
|
||||
export const ExpenseAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
};
|
||||
|
||||
export const CashflowAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Delete: 'Delete',
|
||||
};
|
||||
|
||||
export const ReportsAction = {
|
||||
ALL: 'all',
|
||||
READ_BALANCE_SHEET: 'read-balance-sheet',
|
||||
READ_TRIAL_BALANCE_SHEET: 'read-trial-balance-sheet',
|
||||
READ_PROFIT_LOSS: 'read-profit-loss',
|
||||
READ_JOURNAL: 'read-journal',
|
||||
READ_GENERAL_LEDGET: 'read-general-ledger',
|
||||
READ_CASHFLOW: 'read-cashflow',
|
||||
READ_AR_AGING_SUMMARY: 'read-ar-aging-summary',
|
||||
READ_AP_AGING_SUMMARY: 'read-ap-aging-summary',
|
||||
READ_PURCHASES_BY_ITEMS: 'read-purchases-by-items',
|
||||
READ_SALES_BY_ITEMS: 'read-sales-by-items',
|
||||
READ_CUSTOMERS_TRANSACTIONS: 'read-customers-transactions',
|
||||
READ_VENDORS_TRANSACTIONS: 'read-vendors-transactions',
|
||||
READ_CUSTOMERS_SUMMARY_BALANCE: 'read-customers-summary-balance',
|
||||
READ_VENDORS_SUMMARY_BALANCE: 'read-vendors-summary-balance',
|
||||
READ_INVENTORY_VALUATION_SUMMARY: 'read-inventory-valuation-summary',
|
||||
READ_INVENTORY_ITEM_DETAILS: 'read-inventory-item-details',
|
||||
READ_CASHFLOW_ACCOUNT_TRANSACTION: 'read-cashflow-account-transactions',
|
||||
};
|
||||
|
||||
export const PreferencesAbility = {
|
||||
Mutate: 'Mutate',
|
||||
};
|
||||
|
||||
export const ExchangeRateAbility = {
|
||||
View: 'view',
|
||||
Create: 'create',
|
||||
Delete: 'delete',
|
||||
};
|
||||
|
||||
export const SubscriptionBillingAbility = {
|
||||
View: 'view',
|
||||
Payment: 'payment',
|
||||
};
|
||||
@@ -1,225 +0,0 @@
|
||||
export const ACCOUNT_TYPE = {
|
||||
CASH: 'cash',
|
||||
BANK: 'bank',
|
||||
ACCOUNTS_RECEIVABLE: 'accounts-receivable',
|
||||
INVENTORY: 'inventory',
|
||||
OTHER_CURRENT_ASSET: 'other-ACCOUNT_PARENT_TYPE.CURRENT_ASSET',
|
||||
FIXED_ASSET: 'fixed-asset',
|
||||
NON_CURRENT_ASSET: 'non-ACCOUNT_PARENT_TYPE.CURRENT_ASSET',
|
||||
|
||||
ACCOUNTS_PAYABLE: 'accounts-payable',
|
||||
CREDIT_CARD: 'credit-card',
|
||||
TAX_PAYABLE: 'tax-payable',
|
||||
OTHER_CURRENT_LIABILITY: 'other-current-liability',
|
||||
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||
|
||||
EQUITY: 'equity',
|
||||
INCOME: 'income',
|
||||
OTHER_INCOME: 'other-income',
|
||||
COST_OF_GOODS_SOLD: 'cost-of-goods-sold',
|
||||
EXPENSE: 'expense',
|
||||
OTHER_EXPENSE: 'other-expense',
|
||||
};
|
||||
|
||||
export const ACCOUNT_PARENT_TYPE = {
|
||||
CURRENT_ASSET: 'current-asset',
|
||||
FIXED_ASSET: 'fixed-asset',
|
||||
NON_CURRENT_ASSET: 'non-ACCOUNT_PARENT_TYPE.CURRENT_ASSET',
|
||||
|
||||
CURRENT_LIABILITY: 'current-liability',
|
||||
LOGN_TERM_LIABILITY: 'long-term-liability',
|
||||
NON_CURRENT_LIABILITY: 'non-current-liability',
|
||||
|
||||
EQUITY: 'equity',
|
||||
EXPENSE: 'expense',
|
||||
INCOME: 'income',
|
||||
};
|
||||
|
||||
export const ACCOUNT_ROOT_TYPE = {
|
||||
ASSET: 'asset',
|
||||
LIABILITY: 'liability',
|
||||
EQUITY: 'equity',
|
||||
EXPENSE: 'expene',
|
||||
INCOME: 'income',
|
||||
};
|
||||
|
||||
export const ACCOUNT_NORMAL = {
|
||||
CREDIT: 'credit',
|
||||
DEBIT: 'debit',
|
||||
};
|
||||
export const ACCOUNT_TYPES = [
|
||||
{
|
||||
label: 'Cash',
|
||||
key: ACCOUNT_TYPE.CASH,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Bank',
|
||||
key: ACCOUNT_TYPE.BANK,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Accounts Receivable',
|
||||
key: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Inventory',
|
||||
key: ACCOUNT_TYPE.INVENTORY,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Other Current Asset',
|
||||
key: ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Fixed Asset',
|
||||
key: ACCOUNT_TYPE.FIXED_ASSET,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Non-Current Asset',
|
||||
key: ACCOUNT_TYPE.NON_CURRENT_ASSET,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.ASSET,
|
||||
parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Accounts Payable',
|
||||
key: ACCOUNT_TYPE.ACCOUNTS_PAYABLE,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Credit Card',
|
||||
key: ACCOUNT_TYPE.CREDIT_CARD,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Tax Payable',
|
||||
key: ACCOUNT_TYPE.TAX_PAYABLE,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Other Current Liability',
|
||||
key: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Long Term Liability',
|
||||
key: ACCOUNT_TYPE.LOGN_TERM_LIABILITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.LOGN_TERM_LIABILITY,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Non-Current Liability',
|
||||
key: ACCOUNT_TYPE.NON_CURRENT_LIABILITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.LIABILITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.NON_CURRENT_LIABILITY,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Equity',
|
||||
key: ACCOUNT_TYPE.EQUITY,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EQUITY,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EQUITY,
|
||||
balanceSheet: true,
|
||||
incomeSheet: false,
|
||||
},
|
||||
{
|
||||
label: 'Income',
|
||||
key: ACCOUNT_TYPE.INCOME,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Other Income',
|
||||
key: ACCOUNT_TYPE.OTHER_INCOME,
|
||||
normal: ACCOUNT_NORMAL.CREDIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.INCOME,
|
||||
parentType: ACCOUNT_PARENT_TYPE.INCOME,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Cost of Goods Sold',
|
||||
key: ACCOUNT_TYPE.COST_OF_GOODS_SOLD,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Expense',
|
||||
key: ACCOUNT_TYPE.EXPENSE,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
{
|
||||
label: 'Other Expense',
|
||||
key: ACCOUNT_TYPE.OTHER_EXPENSE,
|
||||
normal: ACCOUNT_NORMAL.DEBIT,
|
||||
rootType: ACCOUNT_ROOT_TYPE.EXPENSE,
|
||||
parentType: ACCOUNT_PARENT_TYPE.EXPENSE,
|
||||
balanceSheet: false,
|
||||
incomeSheet: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const FOREIGN_CURRENCY_ACCOUNTS = ['cash', 'bank'];
|
||||
@@ -1,6 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export default [
|
||||
{ name: intl.get('decrement'), value: 'decrement' },
|
||||
{ name: intl.get('increment'), value: 'increment' },
|
||||
]
|
||||
@@ -1,6 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export default [
|
||||
{ name: intl.get('bills'), value: 'Bill' },
|
||||
{ name: intl.get('expenses'), value: 'Expense' },
|
||||
];
|
||||
@@ -1,40 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export const addMoneyIn = [
|
||||
{
|
||||
name: intl.get('cash_flow.owner_contribution'),
|
||||
value: 'owner_contribution',
|
||||
},
|
||||
{
|
||||
name: intl.get('cash_flow.other_income'),
|
||||
value: 'other_income',
|
||||
},
|
||||
{
|
||||
name: intl.get('cash_flow.transfer_form_account'),
|
||||
value: 'transfer_from_account',
|
||||
},
|
||||
];
|
||||
|
||||
export const addMoneyOut = [
|
||||
{
|
||||
name: intl.get('cash_flow.owner_drawings'),
|
||||
value: 'OwnerDrawing',
|
||||
},
|
||||
{
|
||||
name: intl.get('cash_flow.expenses'),
|
||||
value: 'other_expense',
|
||||
},
|
||||
{
|
||||
name: intl.get('cash_flow.transfer_to_account'),
|
||||
value: 'transfer_to_account',
|
||||
},
|
||||
];
|
||||
|
||||
export const TRANSACRIONS_TYPE = [
|
||||
'OwnerContribution',
|
||||
'OtherIncome',
|
||||
'TransferFromAccount',
|
||||
'OnwersDrawing',
|
||||
'OtherExpense',
|
||||
'TransferToAccount',
|
||||
];
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
|
||||
export const CellType = {
|
||||
Text: 'text',
|
||||
Field: 'field',
|
||||
Button: 'button'
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
import { Classes } from '@blueprintjs/core';
|
||||
|
||||
const CLASSES = {
|
||||
DASHBOARD_PAGE: 'dashboard__page',
|
||||
DASHBOARD_DATATABLE: 'dashboard__datatable',
|
||||
DASHBOARD_CARD: 'dashboard__card',
|
||||
DASHBOARD_CARD_PAGE: 'dashboard__card--page',
|
||||
|
||||
DATATABLE_EDITOR: 'datatable-editor',
|
||||
DATATABLE_EDITOR_ACTIONS: 'datatable-editor__actions',
|
||||
DATATABLE_EDITOR_ITEMS_ENTRIES: 'items-entries-table',
|
||||
DATATABLE_EDITOR_HAS_TOTAL_ROW: 'has-total-row',
|
||||
|
||||
DASHBOARD_CONTENT: 'dashboard-content',
|
||||
DASHBOARD_CONTENT_PREFERENCES: 'dashboard-content--preferences',
|
||||
DASHBOARD_CONTENT_PANE: 'Pane2',
|
||||
DASHBOARD_CENTERED_EMPTY_STATUS: 'dashboard__centered-empty-status',
|
||||
|
||||
PAGE_FORM: 'page-form',
|
||||
PAGE_FORM_HEADER: 'page-form__header',
|
||||
PAGE_FORM_HEADER_PRIMARY: 'page-form__primary-section',
|
||||
PAGE_FORM_HEADER_FIELDS: 'page-form__header-fields',
|
||||
PAGE_FORM_HEADER_BIG_NUMBERS: 'page-form__big-numbers',
|
||||
PAGE_FORM_TABS: 'page-form__tabs',
|
||||
PAGE_FORM_BODY: 'page-form__body',
|
||||
PAGE_FORM_STRIP_STYLE: 'page-form--strip',
|
||||
|
||||
PAGE_FORM_FOOTER: 'page-form__footer',
|
||||
PAGE_FORM_FLOATING_ACTIONS: 'page-form__floating-actions',
|
||||
|
||||
PAGE_FORM_BILL: 'page-form--bill',
|
||||
PAGE_FORM_ESTIMATE: 'page-form--estimate',
|
||||
PAGE_FORM_INVOICE: 'page-form--invoice',
|
||||
PAGE_FORM_RECEIPT: 'page-form--receipt',
|
||||
PAGE_FORM_PAYMENT_MADE: 'page-form--payment-made',
|
||||
PAGE_FORM_PAYMENT_RECEIVE: 'page-form--payment-receive',
|
||||
PAGE_FORM_CUSTOMER: 'page-form--customer',
|
||||
PAGE_FORM_VENDOR: 'page-form--vendor',
|
||||
PAGE_FORM_ITEM: 'page-form--item',
|
||||
PAGE_FORM_MAKE_JOURNAL: 'page-form--make-journal-entries',
|
||||
PAGE_FORM_EXPENSE: 'page-form--expense',
|
||||
PAGE_FORM_CREDIT_NOTE:'page-form--credit-note',
|
||||
PAGE_FORM_VENDOR_CREDIT_NOTE:'page-form--vendor-credit-note',
|
||||
PAGE_FORM_WAREHOUSE_TRANSFER:'page-form--warehouse-transfer',
|
||||
|
||||
FORM_GROUP_LIST_SELECT: 'form-group--select-list',
|
||||
|
||||
CLOUD_SPINNER: 'cloud-spinner',
|
||||
IS_LOADING: 'is-loading',
|
||||
|
||||
DATATABLE_EMPTY_STATUS: 'datatable-empty-status',
|
||||
DATATABLE_EMPTY_STATUS_TITLE: 'datatable-empty-status__title',
|
||||
DATATABLE_EMPTY_STATUS_DESC: 'datatable-empty-status__desc',
|
||||
DATATABLE_EMPTY_STATUS_ACTIONS: 'datatable-empty-status__actions',
|
||||
|
||||
SELECT_LIST_FILL_POPOVER: 'select-list--fill-popover',
|
||||
|
||||
PREFERENCES_PAGE: 'preferences-page',
|
||||
PREFERENCES_PAGE_SIDEBAR: 'preferences-page__sidebar',
|
||||
PREFERENCES_PAGE_TOPBAR: 'preferences-page__topbar',
|
||||
PREFERENCES_PAGE_CONTENT: 'preferences-page__content',
|
||||
PREFERENCES_PAGE_TABS: 'preferences-page__tabs',
|
||||
|
||||
PREFERENCES_SIDEBAR: 'preferences-sidebar',
|
||||
PREFERENCES_TOPBAR: 'preferences-topbar',
|
||||
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT: 'preferences-page__inside-content',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_GENERAL: 'preferences-page__inside-content--general',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_USERS: 'preferences-page__inside-content--users',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_CURRENCIES: 'preferences-page__inside-content--currencies',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT: 'preferences-page__inside-content--accountant',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_SMS_INTEGRATION: 'preferences-page__inside-content--sms-integration',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_ROLES_FORM: 'preferences-page__inside-content--roles-form',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_BRANCHES: 'preferences-page__inside-content--branches',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_WAREHOUSES: 'preferences-page__inside-content--warehouses',
|
||||
|
||||
FINANCIAL_REPORT_INSIDER: 'dashboard__insider--financial-report',
|
||||
|
||||
|
||||
UNIVERSAL_SEARCH: 'universal-search',
|
||||
UNIVERSAL_SEARCH_OMNIBAR: 'universal-search__omnibar',
|
||||
UNIVERSAL_SEARCH_OVERLAY: 'universal-search-overlay',
|
||||
UNIVERSAL_SEARCH_INPUT: 'universal-search__input',
|
||||
UNIVERSAL_SEARCH_INPUT_RIGHT_ELEMENTS: 'universal-search-input-right-elements',
|
||||
UNIVERSAL_SEARCH_TYPE_SELECT_OVERLAY: 'universal-search__type-select-overlay',
|
||||
UNIVERSAL_SEARCH_TYPE_SELECT_BTN: 'universal-search__type-select-btn',
|
||||
UNIVERSAL_SEARCH_FOOTER: 'universal-search__footer',
|
||||
|
||||
UNIVERSAL_SEARCH_ACTIONS: 'universal-search__actions',
|
||||
UNIVERSAL_SEARCH_ACTION_SELECT: 'universal-search__action universal-search__action--select',
|
||||
UNIVERSAL_SEARCH_ACTION_CLOSE: 'universal-search__action universal-search__action--close',
|
||||
UNIVERSAL_SEARCH_ACTION_ARROWS: 'universal-search__action universal-search__action--arrows',
|
||||
|
||||
DIALOG_PDF_PREVIEW: 'dialog--pdf-preview-dialog',
|
||||
|
||||
...Classes,
|
||||
CARD: 'card',
|
||||
ALIGN_RIGHT: 'align-right',
|
||||
FONT_BOLD: 'font-bold',
|
||||
};
|
||||
|
||||
export {
|
||||
CLASSES,
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export default [
|
||||
{ name: intl.get('customer'), path: 'customers' },
|
||||
{ name: intl.get('vendor'), path: 'vendors' },
|
||||
];
|
||||
@@ -1,8 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export const getCountries = () => [
|
||||
{
|
||||
name: intl.get('libya'),
|
||||
value: 'libya',
|
||||
},
|
||||
];
|
||||
@@ -1,23 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
import currencies from 'js-money/lib/currency';
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
export const getCurrencies = () => [
|
||||
{ name: intl.get('us_dollar'), code: 'USD' },
|
||||
{ name: intl.get('euro'), code: 'EUR' },
|
||||
{ name: intl.get('libyan_diner'), code: 'LYD' },
|
||||
];
|
||||
|
||||
export const getAllCurrenciesOptions = () => {
|
||||
const codes = Object.keys(currencies);
|
||||
const sortedCodes = sortBy(codes);
|
||||
|
||||
return sortedCodes.map((code) => {
|
||||
const currency = currencies[code];
|
||||
|
||||
return {
|
||||
key: code,
|
||||
name: `${code} - ${currency.name}`,
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
export const DATATYPES_LENGTH = {
|
||||
STRING: 255,
|
||||
TEXT: 65535,
|
||||
INT_10: 4294967295,
|
||||
DECIMAL_13_3: 9999999999.999,
|
||||
DECIMAL_15_5: 999999999999.999,
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
import moment from 'moment';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export const getDateFormats =()=> [
|
||||
{
|
||||
id: 1,
|
||||
name: intl.get('mm_dd_yy'),
|
||||
label: `${moment().format('MM/DD/YYYY')}`,
|
||||
value: 'mm/dd/yy',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: intl.get('dd_mm_yy'),
|
||||
label: `${moment().format('DD/MM/YYYY')}`,
|
||||
value: 'dd/mm/yy',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: intl.get('yy_mm_dd'),
|
||||
label: `${moment().format('YYYY/MM/DD')}`,
|
||||
value: 'yy/mm/dd',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: intl.get('mm_dd_yy'),
|
||||
label: `${moment().format('MM-DD-YYYY')}`,
|
||||
value: 'mm-dd-yy',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: intl.get('dd_mm_yy_'),
|
||||
label: `${moment().format('DD-MM-YYYY')}`,
|
||||
value: 'dd-mm-yy',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: intl.get('yy_mm_dd_'),
|
||||
label: `${moment().format('YYYY-MM-DD')}`,
|
||||
value: 'yy-mm-dd',
|
||||
},
|
||||
];
|
||||
@@ -1,22 +0,0 @@
|
||||
export const DRAWERS = {
|
||||
ESTIMATE_DRAWER: 'estimate-drawer',
|
||||
MANUAL_JOURNAL_DRAWER: 'journal-drawer',
|
||||
INVOICE_DRAWER: 'invoice-drawer',
|
||||
RECEIPT_DRAWER: 'receipt-drawer',
|
||||
PAYMENT_RECEIVE_DRAWER: 'payment-receive-drawer',
|
||||
ACCOUNT_DRAWER: 'account-drawer',
|
||||
JOURNAL_DRAWER: 'journal-drawer',
|
||||
EXPENSE_DRAWER: 'expense-drawer',
|
||||
BILL_DRAWER: 'bill-drawer',
|
||||
INVENTORY_ADJUSTMENT_DRAWER: 'inventory-adjustment-drawer',
|
||||
CASHFLOW_TRNASACTION_DRAWER: 'cashflow-transaction-drawer',
|
||||
|
||||
QUICK_WRITE_VENDOR: 'quick-write-vendor',
|
||||
QUICK_CREATE_CUSTOMER: 'quick-create-customer',
|
||||
QUICK_CREATE_ITEM: 'quick-create-item',
|
||||
CREDIT_NOTE_DETAIL_DRAWER: 'credit-note-detail-drawer',
|
||||
VENDOR_CREDIT_DETAIL_DRAWER: 'vendor-credit-detail-drawer',
|
||||
REFUND_CREDIT_NOTE_DETAIL_DRAWER:'refund-credit-detail-drawer',
|
||||
REFUND_VENDOR_CREDIT_DETAIL_DRAWER:'refund-vendor-detail-drawer',
|
||||
WAREHOUSE_TRANSFER_DETAIL_DRAWER:'warehouse-transfer-detail-drawer'
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
export const ERROR = {
|
||||
// Sales Estimates
|
||||
ESTIMATE_NUMBER_IS_NOT_UNQIUE: 'ESTIMATE.NUMBER.IS.NOT.UNQIUE',
|
||||
SALE_ESTIMATE_NO_IS_REQUIRED: 'SALE_ESTIMATE_NO_IS_REQUIRED',
|
||||
|
||||
// Sales Invoices
|
||||
SALE_INVOICE_NUMBER_IS_EXISTS: 'SALE.INVOICE.NUMBER.IS.EXISTS',
|
||||
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE',
|
||||
SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE:
|
||||
'SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE',
|
||||
|
||||
// Sales Receipts
|
||||
SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE',
|
||||
SALE_RECEIPT_NO_IS_REQUIRED: 'SALE_RECEIPT_NO_IS_REQUIRED',
|
||||
|
||||
// Bills
|
||||
BILL_NUMBER_EXISTS: 'BILL.NUMBER.EXISTS',
|
||||
SALE_INVOICE_NO_IS_REQUIRED: 'SALE_INVOICE_NO_IS_REQUIRED',
|
||||
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:"ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED",
|
||||
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
|
||||
export const Features = {
|
||||
Warehouses: 'warehouses',
|
||||
Branches: 'branches',
|
||||
ManualJournal: 'manualJournal',
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export const getFiscalYear = () => [
|
||||
{
|
||||
name: `${intl.get('january')} - ${intl.get('december')}`,
|
||||
key: 'january',
|
||||
},
|
||||
{
|
||||
name: `${intl.get('february')} - ${intl.get('january')}`,
|
||||
key: 'february',
|
||||
},
|
||||
{
|
||||
name: `${intl.get('march')} - ${intl.get('february')}`,
|
||||
key: 'march',
|
||||
},
|
||||
{
|
||||
name: `${intl.get('april')} - ${intl.get('march')}`,
|
||||
key: 'april',
|
||||
},
|
||||
{
|
||||
name: `${intl.get('may')} - ${intl.get('april')}`,
|
||||
key: 'may',
|
||||
},
|
||||
{
|
||||
name: `${intl.get('june')} - ${intl.get('may')}`,
|
||||
key: 'june',
|
||||
},
|
||||
{
|
||||
name: `${intl.get('july')} - ${intl.get('june')}`,
|
||||
key: 'july',
|
||||
},
|
||||
{
|
||||
name: `${intl.get('august')} - ${intl.get('july')}`,
|
||||
key: 'august',
|
||||
},
|
||||
{
|
||||
name: `${intl.get('september')} - ${intl.get('august')}`,
|
||||
key: 'september',
|
||||
},
|
||||
{
|
||||
name: `${intl.get('october')} - ${intl.get('november')}`,
|
||||
key: 'october',
|
||||
},
|
||||
{
|
||||
name: `${intl.get('november')} - ${intl.get('october')}`,
|
||||
key: 'november',
|
||||
},
|
||||
{
|
||||
name: `${intl.get('december')} - ${intl.get('november')}`,
|
||||
key: 'december',
|
||||
},
|
||||
]
|
||||
@@ -1,175 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import {
|
||||
SaleInvoiceAction,
|
||||
SaleEstimateAction,
|
||||
AbilitySubject,
|
||||
SaleReceiptAction,
|
||||
CustomerAction,
|
||||
PaymentReceiveAction,
|
||||
BillAction,
|
||||
VendorAction,
|
||||
PaymentMadeAction,
|
||||
AccountAction,
|
||||
ManualJournalAction,
|
||||
ExpenseAction,
|
||||
ItemAction,
|
||||
ReportsAction,
|
||||
} from '../common/abilityOption';
|
||||
|
||||
export const accountsReceivable = [
|
||||
{
|
||||
sectionTitle: <T id={'accounts_receivable_a_r'} />,
|
||||
shortcuts: [
|
||||
{
|
||||
title: <T id={'sales_invoices'} />,
|
||||
description: <T id={'tracking_sales_invoices_with_your_customers'} />,
|
||||
link: '/invoices',
|
||||
subject: AbilitySubject.Invoice,
|
||||
ability: SaleInvoiceAction.View,
|
||||
},
|
||||
{
|
||||
title: <T id={'sales_estimates'} />,
|
||||
description: <T id={'manage_your_sales_estimates_to_create_quotes'} />,
|
||||
link: '/estimates',
|
||||
subject: AbilitySubject.Estimate,
|
||||
ability: SaleEstimateAction.View,
|
||||
},
|
||||
{
|
||||
title: <T id={'sales_receipts'} />,
|
||||
description: <T id={'manage_sales_receipts_for_sales_that_get_paid'} />,
|
||||
link: '/receipts',
|
||||
subject: AbilitySubject.Receipt,
|
||||
ability: SaleReceiptAction.View,
|
||||
},
|
||||
{
|
||||
title: <T id={'customers'} />,
|
||||
description: <T id={'manage_the_customers_relations_with_customer'} />,
|
||||
link: '/customers',
|
||||
subject: AbilitySubject.Customer,
|
||||
ability: CustomerAction.View,
|
||||
},
|
||||
{
|
||||
title: <T id={'customers_payments'} />,
|
||||
description: (
|
||||
<T id={'manage_payment_transactions_from_your_customers'} />
|
||||
),
|
||||
link: '/payment-receives',
|
||||
subject: AbilitySubject.PaymentReceive,
|
||||
ability: PaymentReceiveAction.View,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const accountsPayable = [
|
||||
{
|
||||
sectionTitle: <T id={'accounts_payable_a_p'} />,
|
||||
shortcuts: [
|
||||
{
|
||||
title: <T id={'purchase_invoices'} />,
|
||||
description: (
|
||||
<T id={'manage_the_purchase_invoices_with_your_vendors'} />
|
||||
),
|
||||
link: '/bills',
|
||||
subject: AbilitySubject.Bill,
|
||||
ability: BillAction.View,
|
||||
},
|
||||
{
|
||||
title: <T id={'vendors'} />,
|
||||
description: (
|
||||
<T id={'manage_the_vendors_relations_with_vendor_relations'} />
|
||||
),
|
||||
link: '/vendors',
|
||||
subject: AbilitySubject.Vendor,
|
||||
ability: VendorAction.View,
|
||||
},
|
||||
{
|
||||
title: <T id={'vendors_payments'} />,
|
||||
description: <T id={'manage_payments_transactions_to_your_vendors'} />,
|
||||
link: '/payment-mades',
|
||||
subject: AbilitySubject.PaymentMade,
|
||||
ability: PaymentMadeAction.View,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const financialAccounting = [
|
||||
{
|
||||
sectionTitle: <T id={'financial_accounting'} />,
|
||||
shortcuts: [
|
||||
{
|
||||
title: <T id={'chart_of_accounts'} />,
|
||||
description: (
|
||||
<T
|
||||
id={
|
||||
'manage_your_accounts_chart_to_record_your_transactions_and_categories'
|
||||
}
|
||||
/>
|
||||
),
|
||||
link: '/accounts',
|
||||
subject: AbilitySubject.Account,
|
||||
ability: AccountAction.View,
|
||||
},
|
||||
{
|
||||
title: <T id={'manual_journal'} />,
|
||||
description: (
|
||||
<T id={'manage_manual_journal_transactions_on_accounts'} />
|
||||
),
|
||||
link: '/manual-journals',
|
||||
subject: AbilitySubject.ManualJournal,
|
||||
ability: ManualJournalAction.View,
|
||||
},
|
||||
{
|
||||
title: <T id={'expenses'} />,
|
||||
description: (
|
||||
<T id={'track_your_indirect_expenses_under_specific_categories'} />
|
||||
),
|
||||
link: '/expenses',
|
||||
subject: AbilitySubject.Expense,
|
||||
ability: ExpenseAction.View,
|
||||
},
|
||||
{
|
||||
title: <T id={'financial_statements'} />,
|
||||
description: (
|
||||
<T id={'show_financial_reports_about_your_organization'} />
|
||||
),
|
||||
link: '/financial-reports',
|
||||
subject: AbilitySubject.Report,
|
||||
ability: ReportsAction.ALL,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const productsServices = [
|
||||
{
|
||||
sectionTitle: <T id={'products_services_inventory'} />,
|
||||
shortcuts: [
|
||||
{
|
||||
title: <T id={'products_services'} />,
|
||||
description: (
|
||||
<T id={'manage_your_products_inventory_or_non_inventory'} />
|
||||
),
|
||||
link: '/items',
|
||||
subject: AbilitySubject.Item,
|
||||
ability: ItemAction.View,
|
||||
},
|
||||
{
|
||||
title: <T id={'products_services_categories'} />,
|
||||
description: <T id={'group_your_products_and_service'} />,
|
||||
link: 'items/categories',
|
||||
},
|
||||
{
|
||||
title: <T id={'inventory_adjustments'} />,
|
||||
description: (
|
||||
<T id={'manage_your_inventory_adjustment_of_inventory_items'} />
|
||||
),
|
||||
link: '/inventory-adjustments',
|
||||
subject: AbilitySubject.InventoryAdjustment,
|
||||
ability: SaleInvoiceAction.View,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -1,6 +0,0 @@
|
||||
export * from './accountTypes';
|
||||
export * from './TableStyle';
|
||||
export * from './features';
|
||||
export * from './cellTypes';
|
||||
|
||||
export const Align = { Left: 'left', Right: 'right', Center: 'center' };
|
||||
@@ -1,51 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
import {
|
||||
AbilitySubject,
|
||||
SaleEstimateAction,
|
||||
SaleReceiptAction,
|
||||
SaleInvoiceAction,
|
||||
BillAction,
|
||||
} from '../common/abilityOption';
|
||||
import { useAbilitiesFilter } from '../hooks';
|
||||
|
||||
export const getItemPaymentTransactions = () => [
|
||||
{
|
||||
name: 'invoices',
|
||||
label: intl.get('invoices'),
|
||||
permission: {
|
||||
subject: AbilitySubject.Invoice,
|
||||
ability: SaleInvoiceAction.View,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'estimates',
|
||||
label: intl.get('estimates'),
|
||||
permission: {
|
||||
subject: AbilitySubject.Estimate,
|
||||
ability: SaleEstimateAction.View,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'receipts',
|
||||
label: intl.get('receipts'),
|
||||
permission: {
|
||||
subject: AbilitySubject.Receipt,
|
||||
ability: SaleReceiptAction.View,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bills',
|
||||
label: intl.get('bills'),
|
||||
permission: {
|
||||
subject: AbilitySubject.Bill,
|
||||
ability: BillAction.View,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const useGetItemPaymentTransactionsMenu = () => {
|
||||
const itemTransactionMenu = getItemPaymentTransactions();
|
||||
const abilitiesFilter = useAbilitiesFilter();
|
||||
|
||||
return abilitiesFilter(itemTransactionMenu);
|
||||
};
|
||||
@@ -1,231 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
import {
|
||||
AbilitySubject,
|
||||
AccountAction,
|
||||
BillAction,
|
||||
CashflowAction,
|
||||
CustomerAction,
|
||||
ExpenseAction,
|
||||
ItemAction,
|
||||
ManualJournalAction,
|
||||
ReportsAction,
|
||||
SaleEstimateAction,
|
||||
SaleInvoiceAction,
|
||||
SaleReceiptAction,
|
||||
VendorAction,
|
||||
} from './abilityOption';
|
||||
|
||||
export default [
|
||||
{
|
||||
shortcut_key: 'Shift + I',
|
||||
description: intl.get('jump_to_the_invoices'),
|
||||
permission: {
|
||||
ability: SaleInvoiceAction.View,
|
||||
subject: AbilitySubject.Invoice,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + E',
|
||||
description: intl.get('jump_to_the_estimates'),
|
||||
permission: {
|
||||
ability: SaleEstimateAction.View,
|
||||
subject: AbilitySubject.Estimate,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + R',
|
||||
description: intl.get('jump_to_the_receipts'),
|
||||
permission: {
|
||||
ability: SaleReceiptAction.View,
|
||||
subject: AbilitySubject.Receipt,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + X',
|
||||
description: intl.get('jump_to_the_expenses'),
|
||||
permission: {
|
||||
ability: ExpenseAction.View,
|
||||
subject: AbilitySubject.Expense,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + C',
|
||||
description: intl.get('jump_to_the_customers'),
|
||||
permission: {
|
||||
ability: CustomerAction.View,
|
||||
subject: AbilitySubject.Customer,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + V',
|
||||
description: intl.get('jump_to_the_vendors'),
|
||||
permission: {
|
||||
ability: VendorAction.View,
|
||||
subject: AbilitySubject.Vendor,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + A',
|
||||
description: intl.get('jump_to_the_chart_of_accounts'),
|
||||
permission: {
|
||||
ability: AccountAction.View,
|
||||
subject: AbilitySubject.Account,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + B',
|
||||
description: intl.get('jump_to_the_bills'),
|
||||
permission: {
|
||||
ability: BillAction.View,
|
||||
subject: AbilitySubject.Bill,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + M',
|
||||
description: intl.get('jump_to_the_manual_journals'),
|
||||
permission: {
|
||||
ability: ManualJournalAction.View,
|
||||
subject: AbilitySubject.ManualJournal,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + W',
|
||||
description: intl.get('jump_to_the_items'),
|
||||
permission: {
|
||||
ability: ItemAction.View,
|
||||
subject: AbilitySubject.Item,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + D',
|
||||
description: intl.get('jump_to_the_add_money_in'),
|
||||
permission: {
|
||||
ability: CashflowAction.Create,
|
||||
subject: AbilitySubject.Cashflow,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + Q',
|
||||
description: intl.get('jump_to_the_add_money_out'),
|
||||
permission: {
|
||||
ability: CashflowAction.Create,
|
||||
subject: AbilitySubject.Cashflow,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + 1',
|
||||
description: intl.get('jump_to_the_balance_sheet'),
|
||||
permission: {
|
||||
ability: ReportsAction.READ_BALANCE_SHEET,
|
||||
subject: AbilitySubject.Report,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + 2',
|
||||
description: intl.get('jump_to_the_profit_loss_sheet'),
|
||||
permission: {
|
||||
ability: ReportsAction.READ_PROFIT_LOSS,
|
||||
subject: AbilitySubject.Report,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + 3',
|
||||
description: intl.get('jump_to_the_journal_sheet'),
|
||||
permission: {
|
||||
ability: ReportsAction.READ_JOURNAL,
|
||||
subject: AbilitySubject.Report,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + 4',
|
||||
description: intl.get('jump_to_the_general_ledger_sheet'),
|
||||
permission: {
|
||||
ability: ReportsAction.READ_GENERAL_LEDGET,
|
||||
subject: AbilitySubject.Report,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Shift + 5',
|
||||
description: intl.get('jump_to_the_trial_balance_sheet'),
|
||||
permission: {
|
||||
ability: ReportsAction.READ_TRIAL_BALANCE_SHEET,
|
||||
subject: AbilitySubject.Report,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Ctrl + Shift + I ',
|
||||
description: intl.get('create_a_new_invoice'),
|
||||
permission: {
|
||||
ability: SaleInvoiceAction.Create,
|
||||
subject: AbilitySubject.Invoice,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Ctrl + Shift + E ',
|
||||
description: intl.get('create_a_new_estimate'),
|
||||
permission: {
|
||||
ability: SaleEstimateAction.Create,
|
||||
subject: AbilitySubject.Estimate,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Ctrl + Shift + R ',
|
||||
description: intl.get('create_a_new_receipt'),
|
||||
permission: {
|
||||
ability: SaleReceiptAction.Create,
|
||||
subject: AbilitySubject.Receipt,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Ctrl + Shift + X ',
|
||||
description: intl.get('create_a_new_expense'),
|
||||
permission: {
|
||||
ability: ExpenseAction.Create,
|
||||
subject: AbilitySubject.Expense,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Ctrl + Shift + C ',
|
||||
description: intl.get('create_a_new_customer'),
|
||||
permission: {
|
||||
ability: CustomerAction.Create,
|
||||
subject: AbilitySubject.Customer,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Ctrl + Shift + V ',
|
||||
description: intl.get('create_a_new_vendor'),
|
||||
permission: {
|
||||
ability: VendorAction.Create,
|
||||
subject: AbilitySubject.Vendor,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Ctrl + Shift + B ',
|
||||
description: intl.get('create_a_new_bill'),
|
||||
permission: {
|
||||
ability: BillAction.Create,
|
||||
subject: AbilitySubject.Bill,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Ctrl + Shift + M ',
|
||||
description: intl.get('create_a_new_journal'),
|
||||
permission: {
|
||||
ability: ManualJournalAction.Create,
|
||||
subject: AbilitySubject.ManualJournal,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Ctrl + Shift + W ',
|
||||
description: intl.get('create_a_new_item'),
|
||||
permission: {
|
||||
ability: ItemAction.Create,
|
||||
subject: AbilitySubject.Item,
|
||||
},
|
||||
},
|
||||
{
|
||||
shortcut_key: 'Ctrl + / ',
|
||||
description: intl.get('close_and_open_sidebar'),
|
||||
},
|
||||
];
|
||||
@@ -1,6 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export const getLanguages = () => [
|
||||
{ name: intl.get('english'), value: 'en' },
|
||||
{ name: intl.get('arabic'), value: 'ar' },
|
||||
];
|
||||
@@ -1,12 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export const moreVertOptions = [
|
||||
{
|
||||
name: intl.get('bad_debt.dialog.bad_debt'),
|
||||
value: 'bad debt',
|
||||
},
|
||||
{
|
||||
name: intl.get('bad_debt.dialog.cancel_bad_debt'),
|
||||
value: 'cancel bad debt',
|
||||
},
|
||||
];
|
||||
@@ -1,21 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export const moneyFormat = [
|
||||
{ key: 'total', text: intl.get('total_rows') },
|
||||
{ key: 'always', text: intl.get('always') },
|
||||
{ key: 'none', text: intl.get('none') },
|
||||
];
|
||||
|
||||
export const negativeFormat = [
|
||||
{ key: 'parentheses', text: '($1000)' },
|
||||
{ key: 'mines', text: '-$1000' },
|
||||
];
|
||||
|
||||
export const decimalPlaces = [
|
||||
{ text: '$1', key: 0 },
|
||||
{ text: '$0.1', key: 1 },
|
||||
{ text: '$0.01', key: 2 },
|
||||
{ text: '$0.001', key: 3 },
|
||||
{ text: '$0.0001', key: 4 },
|
||||
{ text: '$0.00001', key: 5 },
|
||||
];
|
||||
@@ -1,659 +0,0 @@
|
||||
import { chain } from 'lodash';
|
||||
import intl from 'react-intl-universal';
|
||||
import {
|
||||
AbilitySubject,
|
||||
AccountAction,
|
||||
BillAction,
|
||||
CreditNoteAction,
|
||||
CustomerAction,
|
||||
ExpenseAction,
|
||||
ItemAction,
|
||||
ManualJournalAction,
|
||||
PaymentMadeAction,
|
||||
PaymentReceiveAction,
|
||||
ReportsAction,
|
||||
SaleEstimateAction,
|
||||
SaleInvoiceAction,
|
||||
SaleReceiptAction,
|
||||
VendorAction,
|
||||
VendorCreditAction,
|
||||
} from './abilityOption';
|
||||
|
||||
export const ModulePermissionsStyle = {
|
||||
Columns: 'columns',
|
||||
Vertical: 'vertical',
|
||||
};
|
||||
|
||||
const PermissionColumn = {
|
||||
View: 'view',
|
||||
Create: 'create',
|
||||
Delete: 'delete',
|
||||
Edit: 'edit',
|
||||
};
|
||||
|
||||
export const getPermissionsSchema = () => [
|
||||
{
|
||||
label: intl.get('permissions.items_inventory'),
|
||||
type: ModulePermissionsStyle.Columns,
|
||||
serviceFullAccess: true,
|
||||
columns: [
|
||||
{ label: intl.get('permissions.column.view'), key: 'view' },
|
||||
{ label: intl.get('permissions.column.create'), key: 'create' },
|
||||
{ label: intl.get('permissions.column.edit'), key: 'edit' },
|
||||
{ label: intl.get('permissions.column.delete'), key: 'delete' },
|
||||
],
|
||||
services: [
|
||||
{
|
||||
label: intl.get('permissions.items'),
|
||||
subject: AbilitySubject.Item,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: ItemAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: ItemAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: ItemAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: ItemAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: ItemAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: ItemAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: ItemAction.Edit }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.inventory_adjustment'),
|
||||
subject: AbilitySubject.InventoryAdjustment,
|
||||
permissions: [
|
||||
{
|
||||
label: 'View',
|
||||
key: ItemAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: 'Create',
|
||||
key: ItemAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: ItemAction.View }],
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
key: ItemAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: ItemAction.Create }],
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
key: ItemAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: ItemAction.Edit }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.contacts'),
|
||||
type: ModulePermissionsStyle.Columns,
|
||||
serviceFullAccess: true,
|
||||
moduleFullAccess: true,
|
||||
columns: [
|
||||
{ label: intl.get('permissions.column.view'), key: 'view' },
|
||||
{ label: intl.get('permissions.column.create'), key: 'create' },
|
||||
{ label: intl.get('permissions.column.edit'), key: 'edit' },
|
||||
{ label: intl.get('permissions.column.delete'), key: 'delete' },
|
||||
],
|
||||
services: [
|
||||
{
|
||||
label: intl.get('permissions.customers'),
|
||||
subject: AbilitySubject.Customer,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: CustomerAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: CustomerAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: CustomerAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: CustomerAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: CustomerAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: CustomerAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: CustomerAction.Edit }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.vendors'),
|
||||
subject: AbilitySubject.Vendor,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: VendorAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: VendorAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: VendorAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: VendorAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: VendorAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: VendorAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: VendorAction.Edit }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.sales'),
|
||||
type: ModulePermissionsStyle.Columns,
|
||||
serviceFullAccess: true,
|
||||
moduleFullAccess: true,
|
||||
columns: [
|
||||
{ label: intl.get('permissions.column.view'), key: 'view' },
|
||||
{ label: intl.get('permissions.column.create'), key: 'create' },
|
||||
{ label: intl.get('permissions.column.edit'), key: 'edit' },
|
||||
{ label: intl.get('permissions.column.delete'), key: 'delete' },
|
||||
],
|
||||
services: [
|
||||
{
|
||||
label: intl.get('permissions.sale_invoice'),
|
||||
subject: AbilitySubject.Invoice,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: SaleInvoiceAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: SaleInvoiceAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: SaleInvoiceAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: SaleInvoiceAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: SaleInvoiceAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: SaleInvoiceAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: SaleInvoiceAction.Edit }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.written_off_invoice'),
|
||||
key: SaleInvoiceAction.Writeoff,
|
||||
depend: [{ key: SaleInvoiceAction.Edit }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.sale_estimate'),
|
||||
subject: AbilitySubject.Estimate,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: SaleEstimateAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: SaleEstimateAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: SaleEstimateAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: SaleEstimateAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: SaleEstimateAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: SaleEstimateAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: SaleEstimateAction.Edit }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.sale_receipt'),
|
||||
subject: AbilitySubject.Receipt,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: SaleReceiptAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: SaleReceiptAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: SaleReceiptAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: SaleReceiptAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: SaleReceiptAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: SaleReceiptAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: SaleReceiptAction.Edit }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.credit_note'),
|
||||
subject: AbilitySubject.CreditNote,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: CreditNoteAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: CreditNoteAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: CreditNoteAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: CreditNoteAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: CreditNoteAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: CreditNoteAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: CreditNoteAction.Edit }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.refund_credit_note'),
|
||||
key: CreditNoteAction.Refund,
|
||||
depend: [{ key: CreditNoteAction.View }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.payment_receive'),
|
||||
subject: AbilitySubject.PaymentReceive,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: PaymentReceiveAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: PaymentReceiveAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: PaymentReceiveAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: PaymentReceiveAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: PaymentReceiveAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: PaymentReceiveAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: PaymentReceiveAction.Edit }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.purchases'),
|
||||
type: ModulePermissionsStyle.Columns,
|
||||
serviceFullAccess: true,
|
||||
moduleFullAccess: true,
|
||||
columns: [
|
||||
{ label: intl.get('permissions.column.view'), key: 'view' },
|
||||
{ label: intl.get('permissions.column.create'), key: 'create' },
|
||||
{ label: intl.get('permissions.column.edit'), key: 'edit' },
|
||||
{ label: intl.get('permissions.column.delete'), key: 'delete' },
|
||||
],
|
||||
services: [
|
||||
{
|
||||
label: intl.get('permissions.bills'),
|
||||
subject: AbilitySubject.Bill,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: BillAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: BillAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: BillAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: BillAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: BillAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: BillAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: BillAction.Edit }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.vendor_credits'),
|
||||
subject: AbilitySubject.VendorCredit,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: VendorCreditAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: VendorCreditAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: VendorCreditAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: VendorCreditAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: VendorCreditAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: VendorCreditAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: VendorCreditAction.Edit }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.refund_vendor_credit'),
|
||||
key: VendorCreditAction.Refund,
|
||||
depend: [{ key: VendorCreditAction.View }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.payment_made'),
|
||||
subject: AbilitySubject.PaymentMade,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: PaymentMadeAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: PaymentMadeAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: PaymentMadeAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: PaymentMadeAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: PaymentMadeAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: PaymentMadeAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: PaymentMadeAction.Edit }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.financial_accounting'),
|
||||
type: ModulePermissionsStyle.Columns,
|
||||
serviceFullAccess: true,
|
||||
moduleFullAccess: true,
|
||||
columns: [
|
||||
{ label: intl.get('permissions.column.view'), key: 'view' },
|
||||
{ label: intl.get('permissions.column.create'), key: 'create' },
|
||||
{ label: intl.get('permissions.column.edit'), key: 'edit' },
|
||||
{ label: intl.get('permissions.column.delete'), key: 'delete' },
|
||||
],
|
||||
services: [
|
||||
{
|
||||
label: intl.get('permissions.manual_journals'),
|
||||
subject: AbilitySubject.ManualJournal,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: ManualJournalAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: ManualJournalAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: ManualJournalAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: ManualJournalAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: ManualJournalAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: ManualJournalAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: ManualJournalAction.Edit }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.chart_of_accounts'),
|
||||
subject: AbilitySubject.Account,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: AccountAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: AccountAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: AccountAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: AccountAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: AccountAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: AccountAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: AccountAction.Edit }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.transactions_locking'),
|
||||
key: AccountAction.TransactionsLocking,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.expenses'),
|
||||
subject: AbilitySubject.Expense,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.column.view'),
|
||||
key: ExpenseAction.View,
|
||||
relatedColumn: PermissionColumn.View,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.create'),
|
||||
key: ExpenseAction.Create,
|
||||
relatedColumn: PermissionColumn.Create,
|
||||
depend: [{ key: ExpenseAction.View }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.edit'),
|
||||
key: ExpenseAction.Edit,
|
||||
relatedColumn: PermissionColumn.Edit,
|
||||
depend: [{ key: ExpenseAction.Create }],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.column.delete'),
|
||||
key: ExpenseAction.Delete,
|
||||
relatedColumn: PermissionColumn.Delete,
|
||||
depend: [{ key: ExpenseAction.Edit }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.reports'),
|
||||
type: ModulePermissionsStyle.Vertical,
|
||||
serviceFullAccess: true,
|
||||
moduleFullAccess: true,
|
||||
services: [
|
||||
{
|
||||
label: intl.get('permissions.financial_reports'),
|
||||
subject: AbilitySubject.Report,
|
||||
permissions: [
|
||||
{
|
||||
label: intl.get('permissions.balance_sheet'),
|
||||
key: ReportsAction.READ_BALANCE_SHEET,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.trial_balance_sheet'),
|
||||
key: ReportsAction.READ_TRIAL_BALANCE_SHEET,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.profit_loss_sheet'),
|
||||
key: ReportsAction.READ_PROFIT_LOSS,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.cash_flow_sheet'),
|
||||
key: ReportsAction.READ_CASHFLOW,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.journal_sheet'),
|
||||
key: ReportsAction.READ_JOURNAL,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.general_ledger'),
|
||||
key: ReportsAction.READ_GENERAL_LEDGET,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.a_r_aging_summary_report'),
|
||||
key: ReportsAction.READ_AR_AGING_SUMMARY,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.a_r_aging_summary_report'),
|
||||
key: ReportsAction.READ_AP_AGING_SUMMARY,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.purchases_by_items'),
|
||||
key: ReportsAction.READ_PURCHASES_BY_ITEMS,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.sales_by_items'),
|
||||
key: ReportsAction.READ_SALES_BY_ITEMS,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.customers_transactions'),
|
||||
key: ReportsAction.READ_CUSTOMERS_TRANSACTIONS,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.vendors_transactions'),
|
||||
key: ReportsAction.READ_VENDORS_TRANSACTIONS,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.customers_summary_balance'),
|
||||
key: ReportsAction.READ_CUSTOMERS_SUMMARY_BALANCE,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.vendors_summary_balance'),
|
||||
key: ReportsAction.READ_VENDORS_SUMMARY_BALANCE,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.inventory_valuation_summary'),
|
||||
key: ReportsAction.READ_INVENTORY_VALUATION_SUMMARY,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.inventory_items_details'),
|
||||
key: ReportsAction.READ_INVENTORY_ITEM_DETAILS,
|
||||
},
|
||||
{
|
||||
label: intl.get('permissions.cashflow_account_transactions'),
|
||||
key: ReportsAction.READ_CASHFLOW_ACCOUNT_TRANSACTION,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function getPermissionsSchemaService(subject) {
|
||||
const permissions = getPermissionsSchema();
|
||||
|
||||
return chain(permissions)
|
||||
.map((perm) => perm.services)
|
||||
.flatten()
|
||||
.find((service) => service.subject === subject)
|
||||
.value();
|
||||
}
|
||||
|
||||
export function getPermissionsSchemaServices() {
|
||||
const permissions = getPermissionsSchema();
|
||||
|
||||
return chain(permissions)
|
||||
.map((module) => module.services)
|
||||
.flatten()
|
||||
.value();
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
import {
|
||||
AbilitySubject,
|
||||
SaleInvoiceAction,
|
||||
CustomerAction,
|
||||
VendorAction,
|
||||
ManualJournalAction,
|
||||
ExpenseAction,
|
||||
} from '../common/abilityOption';
|
||||
import { useAbilitiesFilter } from '../hooks';
|
||||
|
||||
export const getQuickNewActions = () => [
|
||||
{
|
||||
path: 'invoices/new',
|
||||
name: intl.get('sale_invoice'),
|
||||
permission: {
|
||||
subject: AbilitySubject.Invoice,
|
||||
ability: SaleInvoiceAction.Create,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'bills/new',
|
||||
name: intl.get('purchase_invoice'),
|
||||
permission: {
|
||||
subject: AbilitySubject.Invoice,
|
||||
ability: SaleInvoiceAction.Create,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'make-journal-entry',
|
||||
name: intl.get('manual_journal'),
|
||||
permission: {
|
||||
subject: AbilitySubject.ManualJournal,
|
||||
ability: ManualJournalAction.Create,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'expenses/new',
|
||||
name: intl.get('expense'),
|
||||
permission: {
|
||||
subject: AbilitySubject.Expense,
|
||||
ability: ExpenseAction.Create,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'customers/new',
|
||||
name: intl.get('customer'),
|
||||
permission: {
|
||||
subject: AbilitySubject.Customer,
|
||||
ability: CustomerAction.Create,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'vendors/new',
|
||||
name: intl.get('vendor'),
|
||||
permission: {
|
||||
subject: AbilitySubject.Vendor,
|
||||
ability: VendorAction.Vendor,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Retrieve the dashboard quick new menu items.
|
||||
*/
|
||||
export const useGetQuickNewMenu = () => {
|
||||
const quickNewMenu = getQuickNewActions();
|
||||
const abilitiesFilter = useAbilitiesFilter();
|
||||
|
||||
return abilitiesFilter(quickNewMenu);
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export const getSetupWizardSteps = () => [
|
||||
{
|
||||
label: intl.get('setup.plan.plans'),
|
||||
},
|
||||
{
|
||||
label: intl.get('setup.plan.getting_started'),
|
||||
},
|
||||
{
|
||||
label: intl.get('setup.plan.initializing'),
|
||||
},
|
||||
{
|
||||
label: intl.get('setup.plan.congrats'),
|
||||
},
|
||||
];
|
||||
@@ -1,16 +0,0 @@
|
||||
export const RESOURCES_TYPES = {
|
||||
INVOICE: 'invoice',
|
||||
ESTIMATE: 'estimate',
|
||||
RECEIPT: 'receipt',
|
||||
PAYMENT_RECEIVE: 'payment_receive',
|
||||
PAYMENT_MADE: 'payment_made',
|
||||
CUSTOMER: 'customer',
|
||||
VENDOR: 'vendor',
|
||||
ITEM: 'item',
|
||||
BILL: 'bill',
|
||||
EXPENSE: 'expense',
|
||||
MANUAL_JOURNAL: 'manual_journal',
|
||||
ACCOUNT: 'account',
|
||||
CREDIT_NOTE: 'credit_note',
|
||||
VENDOR_CREDIT:'vendor_credit'
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
// Subscription plans.
|
||||
export const plans = [
|
||||
|
||||
];
|
||||
|
||||
// Payment methods.
|
||||
export const paymentMethods = [
|
||||
|
||||
];
|
||||
@@ -1,26 +0,0 @@
|
||||
export const TABLES = {
|
||||
ITEMS: 'items',
|
||||
INVENTORY_ADJUSTMENTS: 'inventory_adjustment',
|
||||
ESTIMATES: 'estimates',
|
||||
INVOICES: 'invoices',
|
||||
RECEIPTS: 'receipts',
|
||||
PAYMENT_RECEIVES: 'payment_receives',
|
||||
BILLS: 'bills',
|
||||
PAYMENT_MADES: 'payment_mades',
|
||||
CUSTOMER: 'customers',
|
||||
VENDORS: 'vendors',
|
||||
ACCOUNTS: 'accounts',
|
||||
MANUAL_JOURNALS: 'manual_journal',
|
||||
EXPENSES: 'expenses',
|
||||
CASHFLOW_ACCOUNTS: 'cashflow_accounts',
|
||||
CASHFLOW_Transactions: 'cashflow_transactions',
|
||||
CREDIT_NOTES: 'credit_notes',
|
||||
VENDOR_CREDITS: 'vendor_credits',
|
||||
WAREHOUSE_TRANSFERS:'warehouse_transfers'
|
||||
};
|
||||
|
||||
export const TABLE_SIZE = {
|
||||
COMPACT: 'compact',
|
||||
SMALL: 'small',
|
||||
MEDIUM: 'medium',
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
74
src/components/Accounts/AccountMultiSelect.tsx
Normal file
74
src/components/Accounts/AccountMultiSelect.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
// @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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
31
src/components/Accounts/AccountsMultiSelect.tsx
Normal file
31
src/components/Accounts/AccountsMultiSelect.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { MultiSelect } from '../MultiSelectTaggable';
|
||||
|
||||
export function AccountsMultiSelect({ ...multiSelectProps }) {
|
||||
return (
|
||||
<MultiSelect
|
||||
itemRenderer={(
|
||||
item,
|
||||
{ active, selected, handleClick, modifiers, query },
|
||||
) => {
|
||||
return (
|
||||
<MenuItem
|
||||
active={active}
|
||||
icon={selected ? 'tick' : 'blank'}
|
||||
text={item.name}
|
||||
label={item.code}
|
||||
key={item.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
popoverProps={{ minimal: true }}
|
||||
fill={true}
|
||||
tagRenderer={(item) => item.name}
|
||||
resetOnSelect={true}
|
||||
{...multiSelectProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
175
src/components/Accounts/AccountsSelectList.tsx
Normal file
175
src/components/Accounts/AccountsSelectList.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
// @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 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('account-form');
|
||||
}
|
||||
},
|
||||
[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,
|
||||
);
|
||||
174
src/components/Accounts/AccountsSuggestField.tsx
Normal file
174
src/components/Accounts/AccountsSuggestField.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
// @ts-nocheck
|
||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import * as R from 'ramda';
|
||||
import intl from 'react-intl-universal';
|
||||
import classNames from 'classnames';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { Suggest } from '@blueprintjs/select';
|
||||
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { MenuItemNestedText, FormattedMessage as T } from '@/components';
|
||||
import { nestedArrayToflatten, filterAccountsByQuery } from '@/utils';
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
// Handle input value renderer.
|
||||
const handleInputValueRenderer = (inputValue) => {
|
||||
if (inputValue) {
|
||||
return inputValue.name.toString();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// 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 suggest field.
|
||||
*/
|
||||
function AccountsSuggestFieldRoot({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #ownProps
|
||||
accounts,
|
||||
initialAccountId,
|
||||
selectedAccountId,
|
||||
defaultSelectText = intl.formatMessage({ id: 'select_account' }),
|
||||
popoverFill = false,
|
||||
onAccountSelected,
|
||||
|
||||
filterByParentTypes = [],
|
||||
filterByTypes = [],
|
||||
filterByNormal,
|
||||
filterByRootTypes = [],
|
||||
|
||||
allowCreate,
|
||||
|
||||
...suggestProps
|
||||
}) {
|
||||
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],
|
||||
);
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleAccountSelect = useCallback(
|
||||
(account) => {
|
||||
if (account.id) {
|
||||
setSelectedAccount({ ...account });
|
||||
onAccountSelected && onAccountSelected(account);
|
||||
} else {
|
||||
openDialog('account-form');
|
||||
}
|
||||
},
|
||||
[setSelectedAccount, onAccountSelected, openDialog],
|
||||
);
|
||||
|
||||
// Maybe inject new item props to select component.
|
||||
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||
const maybeCreateNewItemFromQuery = allowCreate
|
||||
? createNewItemFromQuery
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Suggest
|
||||
items={filteredAccounts}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
||||
itemRenderer={accountItem}
|
||||
itemPredicate={filterAccountsPredicater}
|
||||
onItemSelect={handleAccountSelect}
|
||||
selectedItem={selectedAccount}
|
||||
inputProps={{ placeholder: defaultSelectText }}
|
||||
resetOnClose={true}
|
||||
fill={true}
|
||||
popoverProps={{ minimal: true, boundary: 'window' }}
|
||||
inputValueRenderer={handleInputValueRenderer}
|
||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||
{...suggestProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const AccountsSuggestField = R.compose(withDialogActions)(
|
||||
AccountsSuggestFieldRoot,
|
||||
);
|
||||
49
src/components/Accounts/AccountsTypesSelect.tsx
Normal file
49
src/components/Accounts/AccountsTypesSelect.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
// @ts-nocheck
|
||||
import React, { useCallback } from 'react';
|
||||
import classNames from 'classnames';
|
||||
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);
|
||||
};
|
||||
|
||||
return (
|
||||
<ListSelect
|
||||
items={accountsTypes}
|
||||
selectedItemProp={'key'}
|
||||
selectedItem={selectedTypeId}
|
||||
textProp={'label'}
|
||||
defaultText={defaultSelectText}
|
||||
onItemSelect={handleItemSelected}
|
||||
itemPredicate={filterAccountTypeItems}
|
||||
disabled={disabled}
|
||||
className={classNames('form-group--select-list', {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './AccountMultiSelect';
|
||||
5
src/components/Accounts/index.tsx
Normal file
5
src/components/Accounts/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './AccountMultiSelect';
|
||||
export * from './AccountsMultiSelect';
|
||||
export * from './AccountsSelectList';
|
||||
export * from './AccountsSuggestField';
|
||||
export * from './AccountsTypesSelect';
|
||||
@@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { MultiSelect } from './MultiSelectTaggable';
|
||||
|
||||
export default function AccountsMultiSelect({ ...multiSelectProps }) {
|
||||
return (
|
||||
<MultiSelect
|
||||
itemRenderer={(
|
||||
item,
|
||||
{ active, selected, handleClick, modifiers, query },
|
||||
) => {
|
||||
return (
|
||||
<MenuItem
|
||||
active={active}
|
||||
icon={selected ? 'tick' : 'blank'}
|
||||
text={item.name}
|
||||
label={item.code}
|
||||
key={item.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
popoverProps={{ minimal: true }}
|
||||
fill={true}
|
||||
tagRenderer={(item) => item.name}
|
||||
resetOnSelect={true}
|
||||
{...multiSelectProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import { MenuItem, Button } from '@blueprintjs/core';
|
||||
import { Select } from '@blueprintjs/select';
|
||||
import * as R from 'ramda';
|
||||
import classNames from 'classnames';
|
||||
import intl from 'react-intl-universal'
|
||||
|
||||
import { MenuItemNestedText, FormattedMessage as T } from 'components';
|
||||
import { filterAccountsByQuery } from './utils';
|
||||
import { nestedArrayToflatten } from 'utils';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
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 AccountsSelectList({
|
||||
// #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('account-form');
|
||||
}
|
||||
},
|
||||
[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 default R.compose(withDialogActions)(AccountsSelectList);
|
||||
@@ -1,173 +0,0 @@
|
||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { Suggest } from '@blueprintjs/select';
|
||||
import intl from 'react-intl-universal';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
import { MenuItemNestedText, FormattedMessage as T } from 'components';
|
||||
import { filterAccountsByQuery } from './utils';
|
||||
import { nestedArrayToflatten } from 'utils';
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
// Handle input value renderer.
|
||||
const handleInputValueRenderer = (inputValue) => {
|
||||
if (inputValue) {
|
||||
return inputValue.name.toString();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// 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 suggest field.
|
||||
*/
|
||||
function AccountsSuggestField({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #ownProps
|
||||
accounts,
|
||||
initialAccountId,
|
||||
selectedAccountId,
|
||||
defaultSelectText = intl.formatMessage({ id: 'select_account' }),
|
||||
popoverFill = false,
|
||||
onAccountSelected,
|
||||
|
||||
filterByParentTypes = [],
|
||||
filterByTypes = [],
|
||||
filterByNormal,
|
||||
filterByRootTypes = [],
|
||||
|
||||
allowCreate,
|
||||
|
||||
...suggestProps
|
||||
}) {
|
||||
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],
|
||||
);
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleAccountSelect = useCallback(
|
||||
(account) => {
|
||||
if (account.id) {
|
||||
setSelectedAccount({ ...account });
|
||||
onAccountSelected && onAccountSelected(account);
|
||||
} else {
|
||||
openDialog('account-form');
|
||||
}
|
||||
},
|
||||
[setSelectedAccount, onAccountSelected, openDialog],
|
||||
);
|
||||
|
||||
// Maybe inject new item props to select component.
|
||||
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||
const maybeCreateNewItemFromQuery = allowCreate
|
||||
? createNewItemFromQuery
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Suggest
|
||||
items={filteredAccounts}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
||||
itemRenderer={accountItem}
|
||||
itemPredicate={filterAccountsPredicater}
|
||||
onItemSelect={handleAccountSelect}
|
||||
selectedItem={selectedAccount}
|
||||
inputProps={{ placeholder: defaultSelectText }}
|
||||
resetOnClose={true}
|
||||
fill={true}
|
||||
popoverProps={{ minimal: true, boundary: 'window' }}
|
||||
inputValueRenderer={handleInputValueRenderer}
|
||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||
{...suggestProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withDialogActions)(AccountsSuggestField);
|
||||
@@ -1,48 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { ListSelect } from 'components';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
export default 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);
|
||||
};
|
||||
|
||||
return (
|
||||
<ListSelect
|
||||
items={accountsTypes}
|
||||
selectedItemProp={'key'}
|
||||
selectedItem={selectedTypeId}
|
||||
textProp={'label'}
|
||||
defaultText={defaultSelectText}
|
||||
onItemSelect={handleItemSelected}
|
||||
itemPredicate={filterAccountTypeItems}
|
||||
disabled={disabled}
|
||||
className={classNames('form-group--select-list', {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const getFilterDropdownSchema = () =>
|
||||
Yup.object().shape({
|
||||
conditions: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
fieldKey: Yup.string(),
|
||||
value: Yup.string().nullable(),
|
||||
condition: Yup.string().nullable(),
|
||||
comparator: Yup.string().nullable(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
14
src/components/AdvancedFilter/AdvancedFilter.schema.tsx
Normal file
14
src/components/AdvancedFilter/AdvancedFilter.schema.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
// @ts-nocheck
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const getFilterDropdownSchema = () =>
|
||||
Yup.object().shape({
|
||||
conditions: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
fieldKey: Yup.string(),
|
||||
value: Yup.string().nullable(),
|
||||
condition: Yup.string().nullable(),
|
||||
comparator: Yup.string().nullable(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Classes } from '@blueprintjs/core';
|
||||
import ListSelect from '../ListSelect';
|
||||
import { getConditionTypeCompatators } from './utils';
|
||||
|
||||
export default function DynamicFilterCompatatorField({
|
||||
dataType,
|
||||
...restProps
|
||||
}) {
|
||||
const options = getConditionTypeCompatators(dataType);
|
||||
|
||||
return (
|
||||
<ListSelect
|
||||
textProp={'label'}
|
||||
selectedItemProp={'value'}
|
||||
items={options}
|
||||
className={Classes.FILL}
|
||||
filterable={false}
|
||||
popoverProps={{
|
||||
inline: true,
|
||||
minimal: true,
|
||||
captureDismiss: true,
|
||||
}}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Classes } from '@blueprintjs/core';
|
||||
import { ListSelect } from '../Select';
|
||||
import { getConditionTypeCompatators } from './utils';
|
||||
|
||||
export default function DynamicFilterCompatatorField({
|
||||
dataType,
|
||||
...restProps
|
||||
}) {
|
||||
const options = getConditionTypeCompatators(dataType);
|
||||
|
||||
return (
|
||||
<ListSelect
|
||||
textProp={'label'}
|
||||
selectedItemProp={'value'}
|
||||
items={options}
|
||||
className={Classes.FILL}
|
||||
filterable={false}
|
||||
popoverProps={{
|
||||
inline: true,
|
||||
minimal: true,
|
||||
captureDismiss: true,
|
||||
}}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Formik, FastField, FieldArray, useFormikContext } from 'formik';
|
||||
import {
|
||||
Button,
|
||||
FormGroup,
|
||||
Classes,
|
||||
InputGroup,
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import { get, first, defaultTo, isEqual, isEmpty } from 'lodash';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Choose, Icon, FormattedMessage as T, ListSelect } from 'components';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
import {
|
||||
AdvancedFilterDropdownProvider,
|
||||
FilterConditionProvider,
|
||||
useFilterCondition,
|
||||
useAdvancedFilterContext,
|
||||
} from './AdvancedFilterDropdownContext';
|
||||
import AdvancedFilterCompatatorField from './AdvancedFilterCompatatorField';
|
||||
import AdvancedFilterValueField from './AdvancedFilterValueField';
|
||||
import {
|
||||
filterConditionRoles,
|
||||
getConditionalsOptions,
|
||||
transformFieldsToOptions,
|
||||
shouldFilterValueFieldUpdate,
|
||||
getConditionTypeCompatators,
|
||||
} from './utils';
|
||||
import { getFilterDropdownSchema } from './AdvancedFilter.schema';
|
||||
import { useAdvancedFilterAutoSubmit } from './components';
|
||||
|
||||
/**
|
||||
* Condition item list renderer.
|
||||
*/
|
||||
function ConditionItemRenderer(condition, { handleClick, modifiers, query }) {
|
||||
return (
|
||||
<MenuItem
|
||||
text={
|
||||
<>
|
||||
<div>{condition.label}</div>
|
||||
<span className="text-hint">{condition.text}</span>
|
||||
</>
|
||||
}
|
||||
key={condition.value}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter condition field.
|
||||
*/
|
||||
function FilterConditionField() {
|
||||
const conditionalsOptions = getConditionalsOptions();
|
||||
const { conditionIndex, getConditionFieldPath } = useFilterCondition();
|
||||
|
||||
const conditionFieldPath = getConditionFieldPath('condition');
|
||||
|
||||
return (
|
||||
<FastField name={conditionFieldPath}>
|
||||
{({ form, field }) => (
|
||||
<FormGroup className={'form-group--condition'}>
|
||||
<Choose>
|
||||
<Choose.When condition={conditionIndex === 0}>
|
||||
<InputGroup disabled value={intl.get('filter.when')} />
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
<ListSelect
|
||||
selectedItem={field.value}
|
||||
textProp={'label'}
|
||||
selectedItemProp={'value'}
|
||||
labelProp={'text'}
|
||||
items={conditionalsOptions}
|
||||
className={Classes.FILL}
|
||||
filterable={false}
|
||||
onItemSelect={(option) => {
|
||||
form.setFieldValue(conditionFieldPath, option.value);
|
||||
}}
|
||||
popoverProps={{
|
||||
inline: true,
|
||||
minimal: true,
|
||||
captureDismiss: true,
|
||||
}}
|
||||
itemRenderer={ConditionItemRenderer}
|
||||
/>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatator field.
|
||||
*/
|
||||
function FilterCompatatorFilter() {
|
||||
const { getConditionFieldPath, fieldMeta } = useFilterCondition();
|
||||
|
||||
const comparatorFieldPath = getConditionFieldPath('comparator');
|
||||
const fieldType = get(fieldMeta, 'fieldType');
|
||||
|
||||
return (
|
||||
<FastField name={comparatorFieldPath}>
|
||||
{({ form, field }) => (
|
||||
<FormGroup className={'form-group--comparator'}>
|
||||
<AdvancedFilterCompatatorField
|
||||
dataType={fieldType}
|
||||
className={Classes.FILL}
|
||||
selectedItem={field.value}
|
||||
onItemSelect={(option) => {
|
||||
form.setFieldValue(comparatorFieldPath, option.value);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes default value of comparator field in the condition row once the
|
||||
* field option changing.
|
||||
*/
|
||||
function useDefaultComparatorFieldValue({
|
||||
getConditionValue,
|
||||
setConditionValue,
|
||||
fieldMeta,
|
||||
}) {
|
||||
const fieldKeyValue = getConditionValue('fieldKey');
|
||||
|
||||
const comparatorsOptions = React.useMemo(
|
||||
() => getConditionTypeCompatators(fieldMeta.fieldType),
|
||||
[fieldMeta.fieldType],
|
||||
);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
if (fieldKeyValue) {
|
||||
const defaultValue = get(first(comparatorsOptions), 'value');
|
||||
setConditionValue('comparator', defaultValue);
|
||||
}
|
||||
}, [fieldKeyValue, setConditionValue, comparatorsOptions]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource fields field.
|
||||
*/
|
||||
function FilterFieldsField() {
|
||||
const {
|
||||
getConditionFieldPath,
|
||||
getConditionValue,
|
||||
setConditionValue,
|
||||
fieldMeta,
|
||||
} = useFilterCondition();
|
||||
|
||||
const { fields } = useAdvancedFilterContext();
|
||||
|
||||
const fieldPath = getConditionFieldPath('fieldKey');
|
||||
const valueFieldPath = getConditionFieldPath('value');
|
||||
|
||||
useDefaultComparatorFieldValue({
|
||||
getConditionValue,
|
||||
setConditionValue,
|
||||
fieldMeta,
|
||||
});
|
||||
|
||||
return (
|
||||
<FastField name={fieldPath}>
|
||||
{({ field, form }) => (
|
||||
<FormGroup className={'form-group--fieldKey'}>
|
||||
<ListSelect
|
||||
selectedItem={field.value}
|
||||
textProp={'label'}
|
||||
selectedItemProp={'value'}
|
||||
items={transformFieldsToOptions(fields)}
|
||||
className={Classes.FILL}
|
||||
onItemSelect={(option) => {
|
||||
form.setFieldValue(fieldPath, option.value);
|
||||
|
||||
// Resets the value field to empty once the field option changing.
|
||||
form.setFieldValue(valueFieldPath, '');
|
||||
}}
|
||||
popoverProps={{
|
||||
inline: true,
|
||||
minimal: true,
|
||||
captureDismiss: true,
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced filter value field.
|
||||
*/
|
||||
function FilterValueField() {
|
||||
const { conditionIndex, fieldMeta, getConditionFieldPath } =
|
||||
useFilterCondition();
|
||||
|
||||
// Can't continue if the given field key is not selected yet.
|
||||
if (!fieldMeta) {
|
||||
return null;
|
||||
}
|
||||
// Field meta type, name and options.
|
||||
const fieldType = get(fieldMeta, 'fieldType');
|
||||
const fieldName = get(fieldMeta, 'name');
|
||||
const options = get(fieldMeta, 'options');
|
||||
|
||||
const valueFieldPath = getConditionFieldPath('value');
|
||||
|
||||
return (
|
||||
<FastField
|
||||
name={valueFieldPath}
|
||||
fieldKey={fieldType} // Pass to shouldUpdate function.
|
||||
shouldUpdate={shouldFilterValueFieldUpdate}
|
||||
>
|
||||
{({ form: { setFieldValue }, field }) => (
|
||||
<FormGroup className={'form-group--value'}>
|
||||
<AdvancedFilterValueField
|
||||
isFocus={conditionIndex === 0}
|
||||
value={field.value}
|
||||
key={'name'}
|
||||
label={fieldName}
|
||||
fieldType={fieldType}
|
||||
options={options}
|
||||
onChange={(value) => {
|
||||
setFieldValue(valueFieldPath, value);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced filter condition line.
|
||||
*/
|
||||
function AdvancedFilterDropdownCondition({ conditionIndex, onRemoveClick }) {
|
||||
// Handle click remove condition.
|
||||
const handleClickRemoveCondition = () => {
|
||||
onRemoveClick && onRemoveClick(conditionIndex);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="filter-dropdown__condition">
|
||||
<FilterConditionProvider conditionIndex={conditionIndex}>
|
||||
<FilterConditionField />
|
||||
<FilterFieldsField />
|
||||
<FilterCompatatorFilter />
|
||||
<FilterValueField />
|
||||
|
||||
<Button
|
||||
icon={<Icon icon="times" iconSize={14} />}
|
||||
minimal={true}
|
||||
onClick={handleClickRemoveCondition}
|
||||
className={'button--remove'}
|
||||
/>
|
||||
</FilterConditionProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced filter dropdown condition.
|
||||
*/
|
||||
function AdvancedFilterDropdownConditions({ push, remove, replace, form }) {
|
||||
const { initialCondition } = useAdvancedFilterContext();
|
||||
|
||||
// Handle remove condition.
|
||||
const handleClickRemoveCondition = (conditionIndex) => {
|
||||
if (form.values.conditions.length > 1) {
|
||||
remove(conditionIndex);
|
||||
} else {
|
||||
replace(0, { ...initialCondition });
|
||||
}
|
||||
};
|
||||
// Handle new condition button click.
|
||||
const handleNewConditionBtnClick = (index) => {
|
||||
push({ ...initialCondition });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="filter-dropdonw__conditions-wrap">
|
||||
<div className={'filter-dropdown__conditions'}>
|
||||
{form.values.conditions.map((condition, index) => (
|
||||
<AdvancedFilterDropdownCondition
|
||||
conditionIndex={index}
|
||||
onRemoveClick={handleClickRemoveCondition}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<AdvancedFilterDropdownFooter onClick={handleNewConditionBtnClick} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced filter dropdown form.
|
||||
*/
|
||||
function AdvancedFilterDropdownForm() {
|
||||
// Advanced filter auto-save.
|
||||
useAdvancedFilterAutoSubmit();
|
||||
|
||||
return (
|
||||
<div className="filter-dropdown__form">
|
||||
<FieldArray
|
||||
name={'conditions'}
|
||||
render={({ ...fieldArrayProps }) => (
|
||||
<AdvancedFilterDropdownConditions {...fieldArrayProps} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced filter dropdown footer.
|
||||
*/
|
||||
function AdvancedFilterDropdownFooter({ onClick }) {
|
||||
// Handle new filter condition button click.
|
||||
const onClickNewFilter = (event) => {
|
||||
onClick && onClick(event);
|
||||
};
|
||||
return (
|
||||
<div className="filter-dropdown__footer">
|
||||
<Button minimal={true} onClick={onClickNewFilter}>
|
||||
<T id={'new_conditional'} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced filter dropdown.
|
||||
*/
|
||||
export function AdvancedFilterDropdown({
|
||||
fields,
|
||||
conditions,
|
||||
defaultFieldKey,
|
||||
defaultComparator,
|
||||
defaultValue,
|
||||
defaultCondition,
|
||||
onFilterChange,
|
||||
}) {
|
||||
// Initial condition.
|
||||
const initialCondition = {
|
||||
fieldKey: defaultFieldKey,
|
||||
comparator: defaultTo(defaultComparator, 'contain'),
|
||||
condition: defaultTo(defaultCondition, 'or'),
|
||||
value: defaultTo(defaultValue, ''),
|
||||
};
|
||||
// Initial conditions.
|
||||
const initialConditions = !isEmpty(conditions)
|
||||
? conditions
|
||||
: [initialCondition, initialCondition];
|
||||
|
||||
const [prevConditions, setPrevConditions] = React.useState(initialConditions);
|
||||
|
||||
// Handle the filter dropdown form submit.
|
||||
const handleFitlerDropdownSubmit = (values) => {
|
||||
const conditions = filterConditionRoles(values.conditions);
|
||||
|
||||
// Campare the current conditions with previous conditions, if they were equal
|
||||
// there is no need to execute `onFilterChange` function.
|
||||
if (!isEqual(prevConditions, conditions)) {
|
||||
onFilterChange && onFilterChange(conditions);
|
||||
setPrevConditions(conditions);
|
||||
}
|
||||
};
|
||||
// Filter dropdown validation schema.
|
||||
const validationSchema = getFilterDropdownSchema();
|
||||
|
||||
// Initial values.
|
||||
const initialValues = {
|
||||
conditions: initialConditions,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="filter-dropdown">
|
||||
<AdvancedFilterDropdownProvider
|
||||
initialCondition={initialCondition}
|
||||
fields={fields}
|
||||
>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
component={AdvancedFilterDropdownForm}
|
||||
onSubmit={handleFitlerDropdownSubmit}
|
||||
/>
|
||||
</AdvancedFilterDropdownProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
398
src/components/AdvancedFilter/AdvancedFilterDropdown.tsx
Normal file
398
src/components/AdvancedFilter/AdvancedFilterDropdown.tsx
Normal file
@@ -0,0 +1,398 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Formik, FastField, FieldArray, useFormikContext } from 'formik';
|
||||
import {
|
||||
Button,
|
||||
FormGroup,
|
||||
Classes,
|
||||
InputGroup,
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import { get, first, defaultTo, isEqual, isEmpty } from 'lodash';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Choose, Icon, FormattedMessage as T, ListSelect } from '@/components';
|
||||
import { useUpdateEffect } from '@/hooks';
|
||||
import {
|
||||
AdvancedFilterDropdownProvider,
|
||||
FilterConditionProvider,
|
||||
useFilterCondition,
|
||||
useAdvancedFilterContext,
|
||||
} from './AdvancedFilterDropdownContext';
|
||||
import AdvancedFilterCompatatorField from './AdvancedFilterCompatatorField';
|
||||
import AdvancedFilterValueField from './AdvancedFilterValueField';
|
||||
import {
|
||||
filterConditionRoles,
|
||||
getConditionalsOptions,
|
||||
transformFieldsToOptions,
|
||||
shouldFilterValueFieldUpdate,
|
||||
getConditionTypeCompatators,
|
||||
} from './utils';
|
||||
import { getFilterDropdownSchema } from './AdvancedFilter.schema';
|
||||
import { useAdvancedFilterAutoSubmit } from './components';
|
||||
|
||||
/**
|
||||
* Condition item list renderer.
|
||||
*/
|
||||
function ConditionItemRenderer(condition, { handleClick, modifiers, query }) {
|
||||
return (
|
||||
<MenuItem
|
||||
text={
|
||||
<>
|
||||
<div>{condition.label}</div>
|
||||
<span className="text-hint">{condition.text}</span>
|
||||
</>
|
||||
}
|
||||
key={condition.value}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter condition field.
|
||||
*/
|
||||
function FilterConditionField() {
|
||||
const conditionalsOptions = getConditionalsOptions();
|
||||
const { conditionIndex, getConditionFieldPath } = useFilterCondition();
|
||||
|
||||
const conditionFieldPath = getConditionFieldPath('condition');
|
||||
|
||||
return (
|
||||
<FastField name={conditionFieldPath}>
|
||||
{({ form, field }) => (
|
||||
<FormGroup className={'form-group--condition'}>
|
||||
<Choose>
|
||||
<Choose.When condition={conditionIndex === 0}>
|
||||
<InputGroup disabled value={intl.get('filter.when')} />
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
<ListSelect
|
||||
selectedItem={field.value}
|
||||
textProp={'label'}
|
||||
selectedItemProp={'value'}
|
||||
labelProp={'text'}
|
||||
items={conditionalsOptions}
|
||||
className={Classes.FILL}
|
||||
filterable={false}
|
||||
onItemSelect={(option) => {
|
||||
form.setFieldValue(conditionFieldPath, option.value);
|
||||
}}
|
||||
popoverProps={{
|
||||
inline: true,
|
||||
minimal: true,
|
||||
captureDismiss: true,
|
||||
}}
|
||||
itemRenderer={ConditionItemRenderer}
|
||||
/>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatator field.
|
||||
*/
|
||||
function FilterCompatatorFilter() {
|
||||
const { getConditionFieldPath, fieldMeta } = useFilterCondition();
|
||||
|
||||
const comparatorFieldPath = getConditionFieldPath('comparator');
|
||||
const fieldType = get(fieldMeta, 'fieldType');
|
||||
|
||||
return (
|
||||
<FastField name={comparatorFieldPath}>
|
||||
{({ form, field }) => (
|
||||
<FormGroup className={'form-group--comparator'}>
|
||||
<AdvancedFilterCompatatorField
|
||||
dataType={fieldType}
|
||||
className={Classes.FILL}
|
||||
selectedItem={field.value}
|
||||
onItemSelect={(option) => {
|
||||
form.setFieldValue(comparatorFieldPath, option.value);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes default value of comparator field in the condition row once the
|
||||
* field option changing.
|
||||
*/
|
||||
function useDefaultComparatorFieldValue({
|
||||
getConditionValue,
|
||||
setConditionValue,
|
||||
fieldMeta,
|
||||
}) {
|
||||
const fieldKeyValue = getConditionValue('fieldKey');
|
||||
|
||||
const comparatorsOptions = React.useMemo(
|
||||
() => getConditionTypeCompatators(fieldMeta.fieldType),
|
||||
[fieldMeta.fieldType],
|
||||
);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
if (fieldKeyValue) {
|
||||
const defaultValue = get(first(comparatorsOptions), 'value');
|
||||
setConditionValue('comparator', defaultValue);
|
||||
}
|
||||
}, [fieldKeyValue, setConditionValue, comparatorsOptions]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource fields field.
|
||||
*/
|
||||
function FilterFieldsField() {
|
||||
const {
|
||||
getConditionFieldPath,
|
||||
getConditionValue,
|
||||
setConditionValue,
|
||||
fieldMeta,
|
||||
} = useFilterCondition();
|
||||
|
||||
const { fields } = useAdvancedFilterContext();
|
||||
|
||||
const fieldPath = getConditionFieldPath('fieldKey');
|
||||
const valueFieldPath = getConditionFieldPath('value');
|
||||
|
||||
useDefaultComparatorFieldValue({
|
||||
getConditionValue,
|
||||
setConditionValue,
|
||||
fieldMeta,
|
||||
});
|
||||
|
||||
return (
|
||||
<FastField name={fieldPath}>
|
||||
{({ field, form }) => (
|
||||
<FormGroup className={'form-group--fieldKey'}>
|
||||
<ListSelect
|
||||
selectedItem={field.value}
|
||||
textProp={'label'}
|
||||
selectedItemProp={'value'}
|
||||
items={transformFieldsToOptions(fields)}
|
||||
className={Classes.FILL}
|
||||
onItemSelect={(option) => {
|
||||
form.setFieldValue(fieldPath, option.value);
|
||||
|
||||
// Resets the value field to empty once the field option changing.
|
||||
form.setFieldValue(valueFieldPath, '');
|
||||
}}
|
||||
popoverProps={{
|
||||
inline: true,
|
||||
minimal: true,
|
||||
captureDismiss: true,
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced filter value field.
|
||||
*/
|
||||
function FilterValueField() {
|
||||
const { conditionIndex, fieldMeta, getConditionFieldPath } =
|
||||
useFilterCondition();
|
||||
|
||||
// Can't continue if the given field key is not selected yet.
|
||||
if (!fieldMeta) {
|
||||
return null;
|
||||
}
|
||||
// Field meta type, name and options.
|
||||
const fieldType = get(fieldMeta, 'fieldType');
|
||||
const fieldName = get(fieldMeta, 'name');
|
||||
const options = get(fieldMeta, 'options');
|
||||
|
||||
const valueFieldPath = getConditionFieldPath('value');
|
||||
|
||||
return (
|
||||
<FastField
|
||||
name={valueFieldPath}
|
||||
fieldKey={fieldType} // Pass to shouldUpdate function.
|
||||
shouldUpdate={shouldFilterValueFieldUpdate}
|
||||
>
|
||||
{({ form: { setFieldValue }, field }) => (
|
||||
<FormGroup className={'form-group--value'}>
|
||||
<AdvancedFilterValueField
|
||||
isFocus={conditionIndex === 0}
|
||||
value={field.value}
|
||||
key={'name'}
|
||||
label={fieldName}
|
||||
fieldType={fieldType}
|
||||
options={options}
|
||||
onChange={(value) => {
|
||||
setFieldValue(valueFieldPath, value);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced filter condition line.
|
||||
*/
|
||||
function AdvancedFilterDropdownCondition({ conditionIndex, onRemoveClick }) {
|
||||
// Handle click remove condition.
|
||||
const handleClickRemoveCondition = () => {
|
||||
onRemoveClick && onRemoveClick(conditionIndex);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="filter-dropdown__condition">
|
||||
<FilterConditionProvider conditionIndex={conditionIndex}>
|
||||
<FilterConditionField />
|
||||
<FilterFieldsField />
|
||||
<FilterCompatatorFilter />
|
||||
<FilterValueField />
|
||||
|
||||
<Button
|
||||
icon={<Icon icon="times" iconSize={14} />}
|
||||
minimal={true}
|
||||
onClick={handleClickRemoveCondition}
|
||||
className={'button--remove'}
|
||||
/>
|
||||
</FilterConditionProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced filter dropdown condition.
|
||||
*/
|
||||
function AdvancedFilterDropdownConditions({ push, remove, replace, form }) {
|
||||
const { initialCondition } = useAdvancedFilterContext();
|
||||
|
||||
// Handle remove condition.
|
||||
const handleClickRemoveCondition = (conditionIndex) => {
|
||||
if (form.values.conditions.length > 1) {
|
||||
remove(conditionIndex);
|
||||
} else {
|
||||
replace(0, { ...initialCondition });
|
||||
}
|
||||
};
|
||||
// Handle new condition button click.
|
||||
const handleNewConditionBtnClick = (index) => {
|
||||
push({ ...initialCondition });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="filter-dropdonw__conditions-wrap">
|
||||
<div className={'filter-dropdown__conditions'}>
|
||||
{form.values.conditions.map((condition, index) => (
|
||||
<AdvancedFilterDropdownCondition
|
||||
conditionIndex={index}
|
||||
onRemoveClick={handleClickRemoveCondition}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<AdvancedFilterDropdownFooter onClick={handleNewConditionBtnClick} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced filter dropdown form.
|
||||
*/
|
||||
function AdvancedFilterDropdownForm() {
|
||||
// Advanced filter auto-save.
|
||||
useAdvancedFilterAutoSubmit();
|
||||
|
||||
return (
|
||||
<div className="filter-dropdown__form">
|
||||
<FieldArray
|
||||
name={'conditions'}
|
||||
render={({ ...fieldArrayProps }) => (
|
||||
<AdvancedFilterDropdownConditions {...fieldArrayProps} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced filter dropdown footer.
|
||||
*/
|
||||
function AdvancedFilterDropdownFooter({ onClick }) {
|
||||
// Handle new filter condition button click.
|
||||
const onClickNewFilter = (event) => {
|
||||
onClick && onClick(event);
|
||||
};
|
||||
return (
|
||||
<div className="filter-dropdown__footer">
|
||||
<Button minimal={true} onClick={onClickNewFilter}>
|
||||
<T id={'new_conditional'} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced filter dropdown.
|
||||
*/
|
||||
export function AdvancedFilterDropdown({
|
||||
fields,
|
||||
conditions,
|
||||
defaultFieldKey,
|
||||
defaultComparator,
|
||||
defaultValue,
|
||||
defaultCondition,
|
||||
onFilterChange,
|
||||
}) {
|
||||
// Initial condition.
|
||||
const initialCondition = {
|
||||
fieldKey: defaultFieldKey,
|
||||
comparator: defaultTo(defaultComparator, 'contain'),
|
||||
condition: defaultTo(defaultCondition, 'or'),
|
||||
value: defaultTo(defaultValue, ''),
|
||||
};
|
||||
// Initial conditions.
|
||||
const initialConditions = !isEmpty(conditions)
|
||||
? conditions
|
||||
: [initialCondition, initialCondition];
|
||||
|
||||
const [prevConditions, setPrevConditions] = React.useState(initialConditions);
|
||||
|
||||
// Handle the filter dropdown form submit.
|
||||
const handleFitlerDropdownSubmit = (values) => {
|
||||
const conditions = filterConditionRoles(values.conditions);
|
||||
|
||||
// Campare the current conditions with previous conditions, if they were equal
|
||||
// there is no need to execute `onFilterChange` function.
|
||||
if (!isEqual(prevConditions, conditions)) {
|
||||
onFilterChange && onFilterChange(conditions);
|
||||
setPrevConditions(conditions);
|
||||
}
|
||||
};
|
||||
// Filter dropdown validation schema.
|
||||
const validationSchema = getFilterDropdownSchema();
|
||||
|
||||
// Initial values.
|
||||
const initialValues = {
|
||||
conditions: initialConditions,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="filter-dropdown">
|
||||
<AdvancedFilterDropdownProvider
|
||||
initialCondition={initialCondition}
|
||||
fields={fields}
|
||||
>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
component={AdvancedFilterDropdownForm}
|
||||
onSubmit={handleFitlerDropdownSubmit}
|
||||
/>
|
||||
</AdvancedFilterDropdownProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { get, keyBy } from 'lodash';
|
||||
import { useFormikContext } from 'formik';
|
||||
|
||||
const AdvancedFilterContext = createContext({});
|
||||
const FilterConditionContext = createContext({});
|
||||
|
||||
/**
|
||||
* Advanced filter dropdown context provider.
|
||||
*/
|
||||
function AdvancedFilterDropdownProvider({
|
||||
initialCondition,
|
||||
fields,
|
||||
...props
|
||||
}) {
|
||||
const fieldsByKey = keyBy(fields, 'key');
|
||||
|
||||
// Retrieve field meta by the given field key.
|
||||
const getFieldMetaByKey = React.useCallback(
|
||||
(key) => get(fieldsByKey, key),
|
||||
[fieldsByKey],
|
||||
);
|
||||
// Provider payload.
|
||||
const provider = { initialCondition, fields, fieldsByKey, getFieldMetaByKey };
|
||||
|
||||
return <AdvancedFilterContext.Provider value={provider} {...props} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter condition row context provider.
|
||||
*/
|
||||
function FilterConditionProvider({ conditionIndex, ...props }) {
|
||||
const { setFieldValue, values } = useFormikContext();
|
||||
const { getFieldMetaByKey } = useAdvancedFilterContext();
|
||||
|
||||
// Condition value path.
|
||||
const conditionPath = `conditions[${conditionIndex}]`;
|
||||
|
||||
// Sets conditions value.
|
||||
const setConditionValue = React.useCallback(
|
||||
(field, value) => {
|
||||
return setFieldValue(`${conditionPath}.${field}`, value);
|
||||
},
|
||||
[conditionPath, setFieldValue],
|
||||
);
|
||||
|
||||
// Retrieve condition field value.
|
||||
const getConditionValue = React.useCallback(
|
||||
(field) => get(values, `${conditionPath}.${field}`),
|
||||
[conditionPath, values],
|
||||
);
|
||||
|
||||
// The current condition field meta.
|
||||
const fieldMeta = React.useMemo(
|
||||
() => getFieldMetaByKey(getConditionValue('fieldKey')),
|
||||
[getFieldMetaByKey, getConditionValue],
|
||||
);
|
||||
|
||||
// Retrieve the condition field path.
|
||||
const getConditionFieldPath = React.useCallback(
|
||||
(field) => `${conditionPath}.${field}`,
|
||||
[conditionPath],
|
||||
);
|
||||
|
||||
// Provider payload.
|
||||
const provider = {
|
||||
fieldMeta,
|
||||
conditionIndex,
|
||||
getConditionValue,
|
||||
getConditionFieldPath,
|
||||
setConditionValue,
|
||||
};
|
||||
return <FilterConditionContext.Provider value={provider} {...props} />;
|
||||
}
|
||||
|
||||
const useFilterCondition = () => useContext(FilterConditionContext);
|
||||
const useAdvancedFilterContext = () => useContext(AdvancedFilterContext);
|
||||
|
||||
export {
|
||||
AdvancedFilterDropdownProvider,
|
||||
FilterConditionProvider,
|
||||
useAdvancedFilterContext,
|
||||
useFilterCondition,
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
// @ts-nocheck
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { get, keyBy } from 'lodash';
|
||||
import { useFormikContext } from 'formik';
|
||||
|
||||
const AdvancedFilterContext = createContext({});
|
||||
const FilterConditionContext = createContext({});
|
||||
|
||||
/**
|
||||
* Advanced filter dropdown context provider.
|
||||
*/
|
||||
function AdvancedFilterDropdownProvider({
|
||||
initialCondition,
|
||||
fields,
|
||||
...props
|
||||
}) {
|
||||
const fieldsByKey = keyBy(fields, 'key');
|
||||
|
||||
// Retrieve field meta by the given field key.
|
||||
const getFieldMetaByKey = React.useCallback(
|
||||
(key) => get(fieldsByKey, key),
|
||||
[fieldsByKey],
|
||||
);
|
||||
// Provider payload.
|
||||
const provider = { initialCondition, fields, fieldsByKey, getFieldMetaByKey };
|
||||
|
||||
return <AdvancedFilterContext.Provider value={provider} {...props} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter condition row context provider.
|
||||
*/
|
||||
function FilterConditionProvider({ conditionIndex, ...props }) {
|
||||
const { setFieldValue, values } = useFormikContext();
|
||||
const { getFieldMetaByKey } = useAdvancedFilterContext();
|
||||
|
||||
// Condition value path.
|
||||
const conditionPath = `conditions[${conditionIndex}]`;
|
||||
|
||||
// Sets conditions value.
|
||||
const setConditionValue = React.useCallback(
|
||||
(field, value) => {
|
||||
return setFieldValue(`${conditionPath}.${field}`, value);
|
||||
},
|
||||
[conditionPath, setFieldValue],
|
||||
);
|
||||
|
||||
// Retrieve condition field value.
|
||||
const getConditionValue = React.useCallback(
|
||||
(field) => get(values, `${conditionPath}.${field}`),
|
||||
[conditionPath, values],
|
||||
);
|
||||
|
||||
// The current condition field meta.
|
||||
const fieldMeta = React.useMemo(
|
||||
() => getFieldMetaByKey(getConditionValue('fieldKey')),
|
||||
[getFieldMetaByKey, getConditionValue],
|
||||
);
|
||||
|
||||
// Retrieve the condition field path.
|
||||
const getConditionFieldPath = React.useCallback(
|
||||
(field) => `${conditionPath}.${field}`,
|
||||
[conditionPath],
|
||||
);
|
||||
|
||||
// Provider payload.
|
||||
const provider = {
|
||||
fieldMeta,
|
||||
conditionIndex,
|
||||
getConditionValue,
|
||||
getConditionFieldPath,
|
||||
setConditionValue,
|
||||
};
|
||||
return <FilterConditionContext.Provider value={provider} {...props} />;
|
||||
}
|
||||
|
||||
const useFilterCondition = () => useContext(FilterConditionContext);
|
||||
const useAdvancedFilterContext = () => useContext(AdvancedFilterContext);
|
||||
|
||||
export {
|
||||
AdvancedFilterDropdownProvider,
|
||||
FilterConditionProvider,
|
||||
useAdvancedFilterContext,
|
||||
useFilterCondition,
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Popover, PopoverInteractionKind, Position } from '@blueprintjs/core';
|
||||
import { AdvancedFilterDropdown } from './AdvancedFilterDropdown';
|
||||
|
||||
/**
|
||||
* Advanced filter popover.
|
||||
*/
|
||||
export function AdvancedFilterPopover({
|
||||
popoverProps,
|
||||
advancedFilterProps,
|
||||
children,
|
||||
}) {
|
||||
return (
|
||||
<Popover
|
||||
minimal={true}
|
||||
content={
|
||||
<AdvancedFilterDropdown
|
||||
{...advancedFilterProps}
|
||||
/>
|
||||
}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
canOutsideClickClose={true}
|
||||
modifiers={{
|
||||
offset: { offset: '0, 4' },
|
||||
}}
|
||||
{...popoverProps}
|
||||
>
|
||||
{children}
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
33
src/components/AdvancedFilter/AdvancedFilterPopover.tsx
Normal file
33
src/components/AdvancedFilter/AdvancedFilterPopover.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Popover, PopoverInteractionKind, Position } from '@blueprintjs/core';
|
||||
import { AdvancedFilterDropdown } from './AdvancedFilterDropdown';
|
||||
|
||||
/**
|
||||
* Advanced filter popover.
|
||||
*/
|
||||
export function AdvancedFilterPopover({
|
||||
popoverProps,
|
||||
advancedFilterProps,
|
||||
children,
|
||||
}) {
|
||||
return (
|
||||
<Popover
|
||||
minimal={true}
|
||||
content={
|
||||
<AdvancedFilterDropdown
|
||||
{...advancedFilterProps}
|
||||
/>
|
||||
}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
canOutsideClickClose={true}
|
||||
modifiers={{
|
||||
offset: { offset: '0, 4' },
|
||||
}}
|
||||
{...popoverProps}
|
||||
>
|
||||
{children}
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Position, Checkbox, InputGroup } from '@blueprintjs/core';
|
||||
import { DateInput } from '@blueprintjs/datetime';
|
||||
import moment from 'moment';
|
||||
import intl from 'react-intl-universal';
|
||||
import { isUndefined } from 'lodash';
|
||||
|
||||
import { useAutofocus } from 'hooks';
|
||||
import { T, Choose, ListSelect } from 'components';
|
||||
import { momentFormatter } from 'utils';
|
||||
|
||||
function AdvancedFilterEnumerationField({ options, value, ...rest }) {
|
||||
return (
|
||||
<ListSelect
|
||||
items={options}
|
||||
selectedItem={value}
|
||||
popoverProps={{
|
||||
fill: true,
|
||||
inline: true,
|
||||
minimal: true,
|
||||
captureDismiss: true,
|
||||
}}
|
||||
defaultText={<T id={'filter.select_option'} />}
|
||||
textProp={'label'}
|
||||
selectedItemProp={'key'}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const IFieldType = {
|
||||
ENUMERATION: 'enumeration',
|
||||
BOOLEAN: 'boolean',
|
||||
NUMBER: 'number',
|
||||
DATE: 'date',
|
||||
};
|
||||
|
||||
function tansformDateValue(date, defaultValue = null) {
|
||||
return date ? moment(date).toDate() : defaultValue;
|
||||
}
|
||||
/**
|
||||
* Advanced filter value field detarminer.
|
||||
*/
|
||||
export default function AdvancedFilterValueField2({
|
||||
value,
|
||||
fieldType,
|
||||
options,
|
||||
onChange,
|
||||
isFocus,
|
||||
}) {
|
||||
const [localValue, setLocalValue] = React.useState(value);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (localValue !== value && !isUndefined(value)) {
|
||||
setLocalValue(value);
|
||||
}
|
||||
}, [localValue, value]);
|
||||
|
||||
// Input field reference.
|
||||
const valueRef = useAutofocus(isFocus);
|
||||
|
||||
const triggerOnChange = (value) => onChange && onChange(value);
|
||||
|
||||
// Handle input change.
|
||||
const handleInputChange = (e) => {
|
||||
if (e.currentTarget.type === 'checkbox') {
|
||||
setLocalValue(e.currentTarget.checked);
|
||||
triggerOnChange(e.currentTarget.checked);
|
||||
} else {
|
||||
setLocalValue(e.currentTarget.value);
|
||||
triggerOnChange(e.currentTarget.value);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle enumeration field type change.
|
||||
const handleEnumerationChange = (option) => {
|
||||
setLocalValue(option.key);
|
||||
triggerOnChange(option.key);
|
||||
};
|
||||
|
||||
// Handle date field change.
|
||||
const handleDateChange = (date) => {
|
||||
const formattedDate = moment(date).format('YYYY/MM/DD');
|
||||
|
||||
setLocalValue(formattedDate);
|
||||
triggerOnChange(formattedDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<Choose>
|
||||
<Choose.When condition={fieldType === IFieldType.ENUMERATION}>
|
||||
<AdvancedFilterEnumerationField
|
||||
options={options}
|
||||
value={localValue}
|
||||
onItemSelect={handleEnumerationChange}
|
||||
/>
|
||||
</Choose.When>
|
||||
|
||||
<Choose.When condition={fieldType === IFieldType.DATE}>
|
||||
<DateInput
|
||||
{...momentFormatter('YYYY/MM/DD')}
|
||||
value={tansformDateValue(localValue)}
|
||||
onChange={handleDateChange}
|
||||
popoverProps={{
|
||||
minimal: true,
|
||||
position: Position.BOTTOM,
|
||||
}}
|
||||
shortcuts={true}
|
||||
placeholder={intl.get('filter.enter_date')}
|
||||
fill={true}
|
||||
inputProps={{
|
||||
fill: true,
|
||||
}}
|
||||
/>
|
||||
</Choose.When>
|
||||
|
||||
<Choose.When condition={fieldType === IFieldType.BOOLEAN}>
|
||||
<Checkbox value={localValue} onChange={handleInputChange} />
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
<InputGroup
|
||||
placeholder={intl.get('filter.value')}
|
||||
onChange={handleInputChange}
|
||||
value={localValue}
|
||||
inputRef={valueRef}
|
||||
/>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
);
|
||||
}
|
||||
132
src/components/AdvancedFilter/AdvancedFilterValueField.tsx
Normal file
132
src/components/AdvancedFilter/AdvancedFilterValueField.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Position, Checkbox, InputGroup } from '@blueprintjs/core';
|
||||
import { DateInput } from '@blueprintjs/datetime';
|
||||
import moment from 'moment';
|
||||
import intl from 'react-intl-universal';
|
||||
import { isUndefined } from 'lodash';
|
||||
|
||||
import { useAutofocus } from '@/hooks';
|
||||
import { T, Choose, ListSelect } from '@/components';
|
||||
import { momentFormatter } from '@/utils';
|
||||
|
||||
function AdvancedFilterEnumerationField({ options, value, ...rest }) {
|
||||
return (
|
||||
<ListSelect
|
||||
items={options}
|
||||
selectedItem={value}
|
||||
popoverProps={{
|
||||
fill: true,
|
||||
inline: true,
|
||||
minimal: true,
|
||||
captureDismiss: true,
|
||||
}}
|
||||
defaultText={<T id={'filter.select_option'} />}
|
||||
textProp={'label'}
|
||||
selectedItemProp={'key'}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const IFieldType = {
|
||||
ENUMERATION: 'enumeration',
|
||||
BOOLEAN: 'boolean',
|
||||
NUMBER: 'number',
|
||||
DATE: 'date',
|
||||
};
|
||||
|
||||
function tansformDateValue(date, defaultValue = null) {
|
||||
return date ? moment(date).toDate() : defaultValue;
|
||||
}
|
||||
/**
|
||||
* Advanced filter value field detarminer.
|
||||
*/
|
||||
export default function AdvancedFilterValueField2({
|
||||
value,
|
||||
fieldType,
|
||||
options,
|
||||
onChange,
|
||||
isFocus,
|
||||
}) {
|
||||
const [localValue, setLocalValue] = React.useState(value);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (localValue !== value && !isUndefined(value)) {
|
||||
setLocalValue(value);
|
||||
}
|
||||
}, [localValue, value]);
|
||||
|
||||
// Input field reference.
|
||||
const valueRef = useAutofocus(isFocus);
|
||||
|
||||
const triggerOnChange = (value) => onChange && onChange(value);
|
||||
|
||||
// Handle input change.
|
||||
const handleInputChange = (e) => {
|
||||
if (e.currentTarget.type === 'checkbox') {
|
||||
setLocalValue(e.currentTarget.checked);
|
||||
triggerOnChange(e.currentTarget.checked);
|
||||
} else {
|
||||
setLocalValue(e.currentTarget.value);
|
||||
triggerOnChange(e.currentTarget.value);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle enumeration field type change.
|
||||
const handleEnumerationChange = (option) => {
|
||||
setLocalValue(option.key);
|
||||
triggerOnChange(option.key);
|
||||
};
|
||||
|
||||
// Handle date field change.
|
||||
const handleDateChange = (date) => {
|
||||
const formattedDate = moment(date).format('YYYY/MM/DD');
|
||||
|
||||
setLocalValue(formattedDate);
|
||||
triggerOnChange(formattedDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<Choose>
|
||||
<Choose.When condition={fieldType === IFieldType.ENUMERATION}>
|
||||
<AdvancedFilterEnumerationField
|
||||
options={options}
|
||||
value={localValue}
|
||||
onItemSelect={handleEnumerationChange}
|
||||
/>
|
||||
</Choose.When>
|
||||
|
||||
<Choose.When condition={fieldType === IFieldType.DATE}>
|
||||
<DateInput
|
||||
{...momentFormatter('YYYY/MM/DD')}
|
||||
value={tansformDateValue(localValue)}
|
||||
onChange={handleDateChange}
|
||||
popoverProps={{
|
||||
minimal: true,
|
||||
position: Position.BOTTOM,
|
||||
}}
|
||||
shortcuts={true}
|
||||
placeholder={intl.get('filter.enter_date')}
|
||||
fill={true}
|
||||
inputProps={{
|
||||
fill: true,
|
||||
}}
|
||||
/>
|
||||
</Choose.When>
|
||||
|
||||
<Choose.When condition={fieldType === IFieldType.BOOLEAN}>
|
||||
<Checkbox value={localValue} onChange={handleInputChange} />
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
<InputGroup
|
||||
placeholder={intl.get('filter.value')}
|
||||
onChange={handleInputChange}
|
||||
value={localValue}
|
||||
inputRef={valueRef}
|
||||
/>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const DEBOUNCE_MS = 100;
|
||||
|
||||
/**
|
||||
* Advanced filter auto-save.
|
||||
*/
|
||||
export function useAdvancedFilterAutoSubmit() {
|
||||
const { submitForm, values } = useFormikContext();
|
||||
const [isSubmit, setIsSubmit] = React.useState(false);
|
||||
|
||||
const debouncedSubmit = React.useCallback(
|
||||
debounce(() => {
|
||||
return submitForm().then(() => setIsSubmit(true));
|
||||
}, DEBOUNCE_MS),
|
||||
[submitForm],
|
||||
);
|
||||
|
||||
React.useEffect(() => debouncedSubmit, [debouncedSubmit, values]);
|
||||
}
|
||||
23
src/components/AdvancedFilter/components.tsx
Normal file
23
src/components/AdvancedFilter/components.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const DEBOUNCE_MS = 100;
|
||||
|
||||
/**
|
||||
* Advanced filter auto-save.
|
||||
*/
|
||||
export function useAdvancedFilterAutoSubmit() {
|
||||
const { submitForm, values } = useFormikContext();
|
||||
const [isSubmit, setIsSubmit] = React.useState(false);
|
||||
|
||||
const debouncedSubmit = React.useCallback(
|
||||
debounce(() => {
|
||||
return submitForm().then(() => setIsSubmit(true));
|
||||
}, DEBOUNCE_MS),
|
||||
[submitForm],
|
||||
);
|
||||
|
||||
React.useEffect(() => debouncedSubmit, [debouncedSubmit, values]);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-nocheck
|
||||
import { ArrayHelpers } from 'formik';
|
||||
import { IPopoverProps } from '@blueprintjs/core';
|
||||
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import intl from 'react-intl-universal';
|
||||
import {
|
||||
defaultFastFieldShouldUpdate,
|
||||
uniqueMultiProps,
|
||||
checkRequiredProperties,
|
||||
} from 'utils';
|
||||
|
||||
// Conditions options.
|
||||
export const getConditionalsOptions = () => [
|
||||
{
|
||||
value: 'and',
|
||||
label: intl.get('and'),
|
||||
text: intl.get('filter.all_filters_must_match'),
|
||||
},
|
||||
{
|
||||
value: 'or',
|
||||
label: intl.get('or'),
|
||||
text: intl.get('filter.atleast_one_filter_must_match'),
|
||||
},
|
||||
];
|
||||
|
||||
export const getBooleanCompatators = () => [
|
||||
{ value: 'is', label: intl.get('is') },
|
||||
{ value: 'is_not', label: intl.get('is_not') },
|
||||
];
|
||||
|
||||
export const getTextCompatators = () => [
|
||||
{ value: 'contain', label: intl.get('contain') },
|
||||
{ value: 'not_contain', label: intl.get('not_contain') },
|
||||
{ value: 'equal', label: intl.get('equals') },
|
||||
{ value: 'not_equal', label: intl.get('not_equals') },
|
||||
{ value: 'starts_with', label: intl.get('starts_with') },
|
||||
{ value: 'ends_with', label: intl.get('ends_with') },
|
||||
];
|
||||
|
||||
export const getDateCompatators = () => [
|
||||
{ value: 'in', label: intl.get('in') },
|
||||
{ value: 'after', label: intl.get('after') },
|
||||
{ value: 'before', label: intl.get('before') },
|
||||
];
|
||||
|
||||
export const getOptionsCompatators = () => [
|
||||
{ value: 'is', label: intl.get('is') },
|
||||
{ value: 'is_not', label: intl.get('is_not') },
|
||||
];
|
||||
|
||||
export const getNumberCampatators = () => [
|
||||
{ value: 'equal', label: intl.get('equals') },
|
||||
{ value: 'not_equal', label: intl.get('not_equal') },
|
||||
{ value: 'bigger_than', label: intl.get('bigger_than') },
|
||||
{ value: 'bigger_or_equal', label: intl.get('bigger_or_equals') },
|
||||
{ value: 'smaller_than', label: intl.get('smaller_than') },
|
||||
{ value: 'smaller_or_equal', label: intl.get('smaller_or_equals') },
|
||||
];
|
||||
|
||||
export const getConditionTypeCompatators = (
|
||||
dataType,
|
||||
) => {
|
||||
return [
|
||||
...(dataType === 'enumeration'
|
||||
? [...getOptionsCompatators()]
|
||||
: dataType === 'date'
|
||||
? [...getDateCompatators()]
|
||||
: dataType === 'boolean'
|
||||
? [...getBooleanCompatators()]
|
||||
: dataType === 'number'
|
||||
? [...getNumberCampatators()]
|
||||
: [...getTextCompatators()]),
|
||||
];
|
||||
};
|
||||
|
||||
export const getConditionDefaultCompatator = (
|
||||
dataType,
|
||||
) => {
|
||||
const compatators = getConditionTypeCompatators(dataType);
|
||||
return compatators[0];
|
||||
};
|
||||
|
||||
export const transformFieldsToOptions = (fields) =>
|
||||
fields.map((field) => ({
|
||||
value: field.key,
|
||||
label: field.name,
|
||||
}));
|
||||
|
||||
/**
|
||||
* Filtered conditions that don't contain atleast on required fields or
|
||||
* fileds keys that not exists.
|
||||
* @param {IFilterRole[]} conditions
|
||||
* @returns
|
||||
*/
|
||||
export const filterConditionRoles = (
|
||||
conditions,
|
||||
) => {
|
||||
const requiredProps = ['fieldKey', 'condition', 'comparator', 'value'];
|
||||
|
||||
const filteredConditions = conditions.filter(
|
||||
(condition) =>
|
||||
!checkRequiredProperties(condition, requiredProps),
|
||||
);
|
||||
return uniqueMultiProps(filteredConditions, requiredProps);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines the value field when should update.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const shouldFilterValueFieldUpdate = (newProps, oldProps) => {
|
||||
return (
|
||||
newProps.fieldKey !== oldProps.fieldKey ||
|
||||
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||
);
|
||||
};
|
||||
113
src/components/AdvancedFilter/utils.tsx
Normal file
113
src/components/AdvancedFilter/utils.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
// @ts-nocheck
|
||||
import intl from 'react-intl-universal';
|
||||
import {
|
||||
defaultFastFieldShouldUpdate,
|
||||
uniqueMultiProps,
|
||||
checkRequiredProperties,
|
||||
} from '@/utils';
|
||||
|
||||
// Conditions options.
|
||||
export const getConditionalsOptions = () => [
|
||||
{
|
||||
value: 'and',
|
||||
label: intl.get('and'),
|
||||
text: intl.get('filter.all_filters_must_match'),
|
||||
},
|
||||
{
|
||||
value: 'or',
|
||||
label: intl.get('or'),
|
||||
text: intl.get('filter.atleast_one_filter_must_match'),
|
||||
},
|
||||
];
|
||||
|
||||
export const getBooleanCompatators = () => [
|
||||
{ value: 'is', label: intl.get('is') },
|
||||
{ value: 'is_not', label: intl.get('is_not') },
|
||||
];
|
||||
|
||||
export const getTextCompatators = () => [
|
||||
{ value: 'contain', label: intl.get('contain') },
|
||||
{ value: 'not_contain', label: intl.get('not_contain') },
|
||||
{ value: 'equal', label: intl.get('equals') },
|
||||
{ value: 'not_equal', label: intl.get('not_equals') },
|
||||
{ value: 'starts_with', label: intl.get('starts_with') },
|
||||
{ value: 'ends_with', label: intl.get('ends_with') },
|
||||
];
|
||||
|
||||
export const getDateCompatators = () => [
|
||||
{ value: 'in', label: intl.get('in') },
|
||||
{ value: 'after', label: intl.get('after') },
|
||||
{ value: 'before', label: intl.get('before') },
|
||||
];
|
||||
|
||||
export const getOptionsCompatators = () => [
|
||||
{ value: 'is', label: intl.get('is') },
|
||||
{ value: 'is_not', label: intl.get('is_not') },
|
||||
];
|
||||
|
||||
export const getNumberCampatators = () => [
|
||||
{ value: 'equal', label: intl.get('equals') },
|
||||
{ value: 'not_equal', label: intl.get('not_equal') },
|
||||
{ value: 'bigger_than', label: intl.get('bigger_than') },
|
||||
{ value: 'bigger_or_equal', label: intl.get('bigger_or_equals') },
|
||||
{ value: 'smaller_than', label: intl.get('smaller_than') },
|
||||
{ value: 'smaller_or_equal', label: intl.get('smaller_or_equals') },
|
||||
];
|
||||
|
||||
export const getConditionTypeCompatators = (
|
||||
dataType,
|
||||
) => {
|
||||
return [
|
||||
...(dataType === 'enumeration'
|
||||
? [...getOptionsCompatators()]
|
||||
: dataType === 'date'
|
||||
? [...getDateCompatators()]
|
||||
: dataType === 'boolean'
|
||||
? [...getBooleanCompatators()]
|
||||
: dataType === 'number'
|
||||
? [...getNumberCampatators()]
|
||||
: [...getTextCompatators()]),
|
||||
];
|
||||
};
|
||||
|
||||
export const getConditionDefaultCompatator = (
|
||||
dataType,
|
||||
) => {
|
||||
const compatators = getConditionTypeCompatators(dataType);
|
||||
return compatators[0];
|
||||
};
|
||||
|
||||
export const transformFieldsToOptions = (fields) =>
|
||||
fields.map((field) => ({
|
||||
value: field.key,
|
||||
label: field.name,
|
||||
}));
|
||||
|
||||
/**
|
||||
* Filtered conditions that don't contain atleast on required fields or
|
||||
* fileds keys that not exists.
|
||||
* @param {IFilterRole[]} conditions
|
||||
* @returns
|
||||
*/
|
||||
export const filterConditionRoles = (
|
||||
conditions,
|
||||
) => {
|
||||
const requiredProps = ['fieldKey', 'condition', 'comparator', 'value'];
|
||||
|
||||
const filteredConditions = conditions.filter(
|
||||
(condition) =>
|
||||
!checkRequiredProperties(condition, requiredProps),
|
||||
);
|
||||
return uniqueMultiProps(filteredConditions, requiredProps);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines the value field when should update.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const shouldFilterValueFieldUpdate = (newProps, oldProps) => {
|
||||
return (
|
||||
newProps.fieldKey !== oldProps.fieldKey ||
|
||||
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||
);
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export function Alert({ title, description, children, intent, className }) {
|
||||
return (
|
||||
<AlertRoot className={clsx(className)} intent={intent}>
|
||||
{title && <AlertTitle>{title}</AlertTitle>}
|
||||
{description && <AlertDesc>{description}</AlertDesc>}
|
||||
{children && <AlertDesc>{children}</AlertDesc>}
|
||||
</AlertRoot>
|
||||
);
|
||||
}
|
||||
|
||||
const AlertRoot = styled.div`
|
||||
border: 1px solid rgb(223, 227, 230);
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
${(props) =>
|
||||
props.intent === 'danger' &&
|
||||
`
|
||||
border-color: rgb(249, 198, 198);
|
||||
background: rgb(255, 248, 248);
|
||||
|
||||
${AlertDesc} {
|
||||
color: #d95759;
|
||||
}
|
||||
${AlertTitle} {
|
||||
color: rgb(205, 43, 49);
|
||||
}
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
props.intent === 'primary' &&
|
||||
`
|
||||
background: #fff;
|
||||
border-color: #98a8ee;
|
||||
|
||||
${AlertTitle} {
|
||||
color: #1a3bd4;
|
||||
}
|
||||
${AlertDesc} {
|
||||
color: #455883;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export const AlertTitle = styled.h3`
|
||||
color: rgb(17, 24, 28);
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
export const AlertDesc = styled.p`
|
||||
color: rgb(104, 112, 118);
|
||||
margin: 0;
|
||||
`;
|
||||
61
src/components/Alert/index.tsx
Normal file
61
src/components/Alert/index.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import clsx from 'classnames';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export function Alert({ title, description, children, intent, className }) {
|
||||
return (
|
||||
<AlertRoot className={clsx(className)} intent={intent}>
|
||||
{title && <AlertTitle>{title}</AlertTitle>}
|
||||
{description && <AlertDesc>{description}</AlertDesc>}
|
||||
{children && <AlertDesc>{children}</AlertDesc>}
|
||||
</AlertRoot>
|
||||
);
|
||||
}
|
||||
|
||||
const AlertRoot = styled.div`
|
||||
border: 1px solid rgb(223, 227, 230);
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
${(props) =>
|
||||
props.intent === 'danger' &&
|
||||
`
|
||||
border-color: rgb(249, 198, 198);
|
||||
background: rgb(255, 248, 248);
|
||||
|
||||
${AlertDesc} {
|
||||
color: #d95759;
|
||||
}
|
||||
${AlertTitle} {
|
||||
color: rgb(205, 43, 49);
|
||||
}
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
props.intent === 'primary' &&
|
||||
`
|
||||
background: #fff;
|
||||
border-color: #98a8ee;
|
||||
|
||||
${AlertTitle} {
|
||||
color: #1a3bd4;
|
||||
}
|
||||
${AlertDesc} {
|
||||
color: #455883;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export const AlertTitle = styled.h3`
|
||||
color: rgb(17, 24, 28);
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
export const AlertDesc = styled.p`
|
||||
color: rgb(104, 112, 118);
|
||||
margin: 0;
|
||||
`;
|
||||
@@ -1,63 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Router, Switch, Route } from 'react-router';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { QueryClientProvider, QueryClient } from 'react-query';
|
||||
import { ReactQueryDevtools } from 'react-query/devtools';
|
||||
|
||||
import 'style/App.scss';
|
||||
import 'moment/locale/ar-ly';
|
||||
import 'moment/locale/es-us';
|
||||
|
||||
import AppIntlLoader from './AppIntlLoader';
|
||||
import PrivateRoute from 'components/Guards/PrivateRoute';
|
||||
import GlobalErrors from 'containers/GlobalErrors/GlobalErrors';
|
||||
import DashboardPrivatePages from 'components/Dashboard/PrivatePages';
|
||||
import Authentication from 'components/Authentication';
|
||||
|
||||
import { SplashScreen, DashboardThemeProvider } from '../components';
|
||||
import { queryConfig } from '../hooks/query/base';
|
||||
|
||||
/**
|
||||
* App inner.
|
||||
*/
|
||||
function AppInsider({ history }) {
|
||||
return (
|
||||
<div className="App">
|
||||
<DashboardThemeProvider>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path={'/auth'} component={Authentication} />
|
||||
<Route path={'/'}>
|
||||
<PrivateRoute component={DashboardPrivatePages} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
|
||||
<GlobalErrors />
|
||||
</DashboardThemeProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Core application.
|
||||
*/
|
||||
export default function App() {
|
||||
// Browser history.
|
||||
const history = createBrowserHistory();
|
||||
|
||||
// Query client.
|
||||
const queryClient = new QueryClient(queryConfig);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SplashScreen />
|
||||
|
||||
<AppIntlLoader>
|
||||
<AppInsider history={history} />
|
||||
</AppIntlLoader>
|
||||
|
||||
<ReactQueryDevtools initialIsOpen />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
64
src/components/App.tsx
Normal file
64
src/components/App.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Router, Switch, Route } from 'react-router';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { QueryClientProvider, QueryClient } from 'react-query';
|
||||
import { ReactQueryDevtools } from 'react-query/devtools';
|
||||
|
||||
import '@/style/App.scss';
|
||||
import 'moment/locale/ar-ly';
|
||||
import 'moment/locale/es-us';
|
||||
|
||||
import AppIntlLoader from './AppIntlLoader';
|
||||
import PrivateRoute from '@/components/Guards/PrivateRoute';
|
||||
import GlobalErrors from '@/containers/GlobalErrors/GlobalErrors';
|
||||
import DashboardPrivatePages from '@/components/Dashboard/PrivatePages';
|
||||
import Authentication from '@/components/Authentication';
|
||||
|
||||
import { SplashScreen, DashboardThemeProvider } from '../components';
|
||||
import { queryConfig } from '../hooks/query/base';
|
||||
|
||||
/**
|
||||
* App inner.
|
||||
*/
|
||||
function AppInsider({ history }) {
|
||||
return (
|
||||
<div className="App">
|
||||
<DashboardThemeProvider>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path={'/auth'} component={Authentication} />
|
||||
<Route path={'/'}>
|
||||
<PrivateRoute component={DashboardPrivatePages} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
|
||||
<GlobalErrors />
|
||||
</DashboardThemeProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Core application.
|
||||
*/
|
||||
export default function App() {
|
||||
// Browser history.
|
||||
const history = createBrowserHistory();
|
||||
|
||||
// Query client.
|
||||
const queryClient = new QueryClient(queryConfig);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SplashScreen />
|
||||
|
||||
<AppIntlLoader>
|
||||
<AppInsider history={history} />
|
||||
</AppIntlLoader>
|
||||
|
||||
<ReactQueryDevtools initialIsOpen />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { setLocale } from 'yup';
|
||||
import intl from 'react-intl-universal';
|
||||
import { find } from 'lodash';
|
||||
import rtlDetect from 'rtl-detect';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { AppIntlProvider } from './AppIntlProvider';
|
||||
import { useSplashLoading } from '../hooks/state';
|
||||
|
||||
import { useWatchImmediate } from '../hooks';
|
||||
import withDashboardActions from '../containers/Dashboard/withDashboardActions';
|
||||
|
||||
const SUPPORTED_LOCALES = [
|
||||
{ name: 'English', value: 'en' },
|
||||
{ name: 'العربية', value: 'ar' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Retrieve the current local.
|
||||
*/
|
||||
function getCurrentLocal() {
|
||||
let currentLocale = intl.determineLocale({
|
||||
urlLocaleKey: 'lang',
|
||||
cookieLocaleKey: 'locale',
|
||||
localStorageLocaleKey: 'lang',
|
||||
});
|
||||
if (!find(SUPPORTED_LOCALES, { value: currentLocale })) {
|
||||
currentLocale = 'en';
|
||||
}
|
||||
return currentLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the localization data of the given locale.
|
||||
*/
|
||||
function loadLocales(currentLocale) {
|
||||
return import(`../lang/${currentLocale}/index.json`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the localization data of yup validation library.
|
||||
*/
|
||||
function loadYupLocales(currentLocale) {
|
||||
return import(`../lang/${currentLocale}/locale`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the html document direction to RTl if it was rtl-language.
|
||||
*/
|
||||
function useDocumentDirectionModifier(locale, isRTL) {
|
||||
React.useEffect(() => {
|
||||
if (isRTL) {
|
||||
const htmlDocument = document.querySelector('html');
|
||||
htmlDocument.setAttribute('dir', 'rtl');
|
||||
htmlDocument.setAttribute('lang', locale);
|
||||
}
|
||||
}, [isRTL, locale]);
|
||||
}
|
||||
|
||||
function transformMomentLocale(currentLocale) {
|
||||
return currentLocale === 'ar' ? 'ar-ly' : currentLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads application locales of the given current locale.
|
||||
* @param {string} currentLocale
|
||||
* @returns {{ isLoading: boolean }}
|
||||
*/
|
||||
function useAppLoadLocales(currentLocale) {
|
||||
const [startLoading, stopLoading] = useSplashLoading();
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Lodas the locales data file.
|
||||
loadLocales(currentLocale)
|
||||
.then((results) => {
|
||||
return intl.init({
|
||||
currentLocale,
|
||||
locales: {
|
||||
[currentLocale]: results,
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
moment.locale(transformMomentLocale(currentLocale));
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [currentLocale, stopLoading]);
|
||||
|
||||
// Watches the value to start/stop splash screen.
|
||||
useWatchImmediate(
|
||||
(value) => (value ? startLoading() : stopLoading()),
|
||||
isLoading,
|
||||
);
|
||||
return { isLoading };
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads application yup locales based on the given current locale.
|
||||
* @param {string} currentLocale
|
||||
* @returns {{ isLoading: boolean }}
|
||||
*/
|
||||
function useAppYupLoadLocales(currentLocale) {
|
||||
const [startLoading, stopLoading] = useSplashLoading();
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
loadYupLocales(currentLocale)
|
||||
.then(({ locale }) => {
|
||||
setLocale(locale);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.then(() => {});
|
||||
}, [currentLocale, stopLoading]);
|
||||
|
||||
// Watches the valiue to start/stop splash screen.
|
||||
useWatchImmediate(
|
||||
(value) => (value ? startLoading() : stopLoading()),
|
||||
isLoading,
|
||||
);
|
||||
return { isLoading };
|
||||
}
|
||||
|
||||
/**
|
||||
* Application Intl loader.
|
||||
*/
|
||||
function AppIntlLoader({ children }) {
|
||||
// Retrieve the current locale.
|
||||
const currentLocale = getCurrentLocal();
|
||||
|
||||
// Detarmines the document direction based on the given locale.
|
||||
const isRTL = rtlDetect.isRtlLang(currentLocale);
|
||||
|
||||
// Modifies the html document direction
|
||||
useDocumentDirectionModifier(currentLocale, isRTL);
|
||||
|
||||
// Loads yup localization of the given locale.
|
||||
const { isLoading: isAppYupLocalesLoading } =
|
||||
useAppYupLoadLocales(currentLocale);
|
||||
|
||||
// Loads application locales of the given locale.
|
||||
const { isLoading: isAppLocalesLoading } = useAppLoadLocales(currentLocale);
|
||||
|
||||
// Detarmines whether the app locales loading.
|
||||
const isLoading = isAppYupLocalesLoading || isAppLocalesLoading;
|
||||
|
||||
return (
|
||||
<AppIntlProvider currentLocale={currentLocale} isRTL={isRTL}>
|
||||
{isLoading ? null : children}
|
||||
</AppIntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withDashboardActions)(AppIntlLoader);
|
||||
157
src/components/AppIntlLoader.tsx
Normal file
157
src/components/AppIntlLoader.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { setLocale } from 'yup';
|
||||
import intl from 'react-intl-universal';
|
||||
import { find } from 'lodash';
|
||||
import rtlDetect from 'rtl-detect';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { AppIntlProvider } from './AppIntlProvider';
|
||||
import { useSplashLoading } from '@/hooks/state';
|
||||
|
||||
import { useWatchImmediate } from '../hooks';
|
||||
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
|
||||
|
||||
const SUPPORTED_LOCALES = [
|
||||
{ name: 'English', value: 'en' },
|
||||
{ name: 'العربية', value: 'ar' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Retrieve the current local.
|
||||
*/
|
||||
function getCurrentLocal() {
|
||||
let currentLocale = intl.determineLocale({
|
||||
urlLocaleKey: 'lang',
|
||||
cookieLocaleKey: 'locale',
|
||||
localStorageLocaleKey: 'lang',
|
||||
});
|
||||
if (!find(SUPPORTED_LOCALES, { value: currentLocale })) {
|
||||
currentLocale = 'en';
|
||||
}
|
||||
return currentLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the localization data of the given locale.
|
||||
*/
|
||||
function loadLocales(currentLocale) {
|
||||
return import(`../lang/${currentLocale}/index.json`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the localization data of yup validation library.
|
||||
*/
|
||||
function loadYupLocales(currentLocale) {
|
||||
return import(`../lang/${currentLocale}/locale`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the html document direction to RTl if it was rtl-language.
|
||||
*/
|
||||
function useDocumentDirectionModifier(locale, isRTL) {
|
||||
React.useEffect(() => {
|
||||
if (isRTL) {
|
||||
const htmlDocument = document.querySelector('html');
|
||||
htmlDocument.setAttribute('dir', 'rtl');
|
||||
htmlDocument.setAttribute('lang', locale);
|
||||
}
|
||||
}, [isRTL, locale]);
|
||||
}
|
||||
|
||||
function transformMomentLocale(currentLocale) {
|
||||
return currentLocale === 'ar' ? 'ar-ly' : currentLocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads application locales of the given current locale.
|
||||
* @param {string} currentLocale
|
||||
* @returns {{ isLoading: boolean }}
|
||||
*/
|
||||
function useAppLoadLocales(currentLocale) {
|
||||
const [startLoading, stopLoading] = useSplashLoading();
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Lodas the locales data file.
|
||||
loadLocales(currentLocale)
|
||||
.then((results) => {
|
||||
return intl.init({
|
||||
currentLocale,
|
||||
locales: {
|
||||
[currentLocale]: results,
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
moment.locale(transformMomentLocale(currentLocale));
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [currentLocale, stopLoading]);
|
||||
|
||||
// Watches the value to start/stop splash screen.
|
||||
useWatchImmediate(
|
||||
(value) => (value ? startLoading() : stopLoading()),
|
||||
isLoading,
|
||||
);
|
||||
return { isLoading };
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads application yup locales based on the given current locale.
|
||||
* @param {string} currentLocale
|
||||
* @returns {{ isLoading: boolean }}
|
||||
*/
|
||||
function useAppYupLoadLocales(currentLocale) {
|
||||
const [startLoading, stopLoading] = useSplashLoading();
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
loadYupLocales(currentLocale)
|
||||
.then(({ locale }) => {
|
||||
setLocale(locale);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.then(() => {});
|
||||
}, [currentLocale, stopLoading]);
|
||||
|
||||
// Watches the valiue to start/stop splash screen.
|
||||
useWatchImmediate(
|
||||
(value) => (value ? startLoading() : stopLoading()),
|
||||
isLoading,
|
||||
);
|
||||
return { isLoading };
|
||||
}
|
||||
|
||||
/**
|
||||
* Application Intl loader.
|
||||
*/
|
||||
function AppIntlLoader({ children }) {
|
||||
// Retrieve the current locale.
|
||||
const currentLocale = getCurrentLocal();
|
||||
|
||||
// Detarmines the document direction based on the given locale.
|
||||
const isRTL = rtlDetect.isRtlLang(currentLocale);
|
||||
|
||||
// Modifies the html document direction
|
||||
useDocumentDirectionModifier(currentLocale, isRTL);
|
||||
|
||||
// Loads yup localization of the given locale.
|
||||
const { isLoading: isAppYupLocalesLoading } =
|
||||
useAppYupLoadLocales(currentLocale);
|
||||
|
||||
// Loads application locales of the given locale.
|
||||
const { isLoading: isAppLocalesLoading } = useAppLoadLocales(currentLocale);
|
||||
|
||||
// Detarmines whether the app locales loading.
|
||||
const isLoading = isAppYupLocalesLoading || isAppLocalesLoading;
|
||||
|
||||
return (
|
||||
<AppIntlProvider currentLocale={currentLocale} isRTL={isRTL}>
|
||||
{isLoading ? null : children}
|
||||
</AppIntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withDashboardActions)(AppIntlLoader);
|
||||
@@ -1,25 +0,0 @@
|
||||
import React, { createContext } from 'react';
|
||||
|
||||
const AppIntlContext = createContext();
|
||||
|
||||
/**
|
||||
* Application intl provider.
|
||||
*/
|
||||
function AppIntlProvider({ currentLocale, isRTL, children }) {
|
||||
const provider = {
|
||||
currentLocale,
|
||||
isRTL,
|
||||
isLTR: !isRTL,
|
||||
direction: isRTL ? 'rtl' : 'ltr',
|
||||
};
|
||||
|
||||
return (
|
||||
<AppIntlContext.Provider value={provider}>
|
||||
{children}
|
||||
</AppIntlContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const useAppIntlContext = () => React.useContext(AppIntlContext);
|
||||
|
||||
export { AppIntlProvider, useAppIntlContext };
|
||||
26
src/components/AppIntlProvider.tsx
Normal file
26
src/components/AppIntlProvider.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
// @ts-nocheck
|
||||
import React, { createContext } from 'react';
|
||||
|
||||
const AppIntlContext = createContext();
|
||||
|
||||
/**
|
||||
* Application intl provider.
|
||||
*/
|
||||
function AppIntlProvider({ currentLocale, isRTL, children }) {
|
||||
const provider = {
|
||||
currentLocale,
|
||||
isRTL,
|
||||
isLTR: !isRTL,
|
||||
direction: isRTL ? 'rtl' : 'ltr',
|
||||
};
|
||||
|
||||
return (
|
||||
<AppIntlContext.Provider value={provider}>
|
||||
{children}
|
||||
</AppIntlContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const useAppIntlContext = () => React.useContext(AppIntlContext);
|
||||
|
||||
export { AppIntlProvider, useAppIntlContext };
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Position, Toaster, Intent } from "@blueprintjs/core";
|
||||
|
||||
const AppToaster = Toaster.create({
|
||||
position: Position.RIGHT_BOTTOM,
|
||||
intent: Intent.WARNING,
|
||||
});
|
||||
|
||||
export default AppToaster;
|
||||
7
src/components/AppToaster/index.tsx
Normal file
7
src/components/AppToaster/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import { Position, Toaster, Intent } from '@blueprintjs/core';
|
||||
|
||||
export const AppToaster = Toaster.create({
|
||||
position: Position.RIGHT_BOTTOM,
|
||||
intent: Intent.WARNING,
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Redirect, Route, Switch, Link, useLocation } from 'react-router-dom';
|
||||
import BodyClassName from 'react-body-classname';
|
||||
import { TransitionGroup, CSSTransition } from 'react-transition-group';
|
||||
import authenticationRoutes from 'routes/authentication';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import Icon from 'components/Icon';
|
||||
import { useIsAuthenticated } from 'hooks/state';
|
||||
import 'style/pages/Authentication/Auth.scss';
|
||||
|
||||
function PageFade(props) {
|
||||
return <CSSTransition {...props} classNames="authTransition" timeout={500} />;
|
||||
}
|
||||
|
||||
export default function AuthenticationWrapper({ ...rest }) {
|
||||
const to = { pathname: '/' };
|
||||
const location = useLocation();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const locationKey = location.pathname;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isAuthenticated ? (
|
||||
<Redirect to={to} />
|
||||
) : (
|
||||
<BodyClassName className={'authentication'}>
|
||||
<div class="authentication-page">
|
||||
<a
|
||||
href={'http://bigcapital.ly'}
|
||||
className={'authentication-page__goto-bigcapital'}
|
||||
>
|
||||
<T id={'go_to_bigcapital_com'} />
|
||||
</a>
|
||||
|
||||
<div class="authentication-page__form-wrapper">
|
||||
<div class="authentication-insider">
|
||||
<div className={'authentication-insider__logo-section'}>
|
||||
<Icon icon="bigcapital" height={37} width={214} />
|
||||
</div>
|
||||
|
||||
<TransitionGroup>
|
||||
<PageFade key={locationKey}>
|
||||
<Switch>
|
||||
{authenticationRoutes.map((route, index) => (
|
||||
<Route
|
||||
key={index}
|
||||
path={route.path}
|
||||
exact={route.exact}
|
||||
component={route.component}
|
||||
/>
|
||||
))}
|
||||
</Switch>
|
||||
</PageFade>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BodyClassName>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
62
src/components/Authentication.tsx
Normal file
62
src/components/Authentication.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Redirect, Route, Switch, Link, useLocation } from 'react-router-dom';
|
||||
import BodyClassName from 'react-body-classname';
|
||||
import { TransitionGroup, CSSTransition } from 'react-transition-group';
|
||||
import authenticationRoutes from '@/routes/authentication';
|
||||
import { Icon, FormattedMessage as T } from '@/components';
|
||||
import { useIsAuthenticated } from '@/hooks/state';
|
||||
import '@/style/pages/Authentication/Auth.scss';
|
||||
|
||||
function PageFade(props) {
|
||||
return <CSSTransition {...props} classNames="authTransition" timeout={500} />;
|
||||
}
|
||||
|
||||
export default function AuthenticationWrapper({ ...rest }) {
|
||||
const to = { pathname: '/' };
|
||||
const location = useLocation();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const locationKey = location.pathname;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isAuthenticated ? (
|
||||
<Redirect to={to} />
|
||||
) : (
|
||||
<BodyClassName className={'authentication'}>
|
||||
<div class="authentication-page">
|
||||
<a
|
||||
href={'http://bigcapital.ly'}
|
||||
className={'authentication-page__goto-bigcapital'}
|
||||
>
|
||||
<T id={'go_to_bigcapital_com'} />
|
||||
</a>
|
||||
|
||||
<div class="authentication-page__form-wrapper">
|
||||
<div class="authentication-insider">
|
||||
<div className={'authentication-insider__logo-section'}>
|
||||
<Icon icon="bigcapital" height={37} width={214} />
|
||||
</div>
|
||||
|
||||
<TransitionGroup>
|
||||
<PageFade key={locationKey}>
|
||||
<Switch>
|
||||
{authenticationRoutes.map((route, index) => (
|
||||
<Route
|
||||
key={index}
|
||||
path={route.path}
|
||||
exact={route.exact}
|
||||
component={route.component}
|
||||
/>
|
||||
))}
|
||||
</Switch>
|
||||
</PageFade>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BodyClassName>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import { firstLettersArgs } from 'utils';
|
||||
|
||||
export default function AvatarCell({ row: { original }, size }) {
|
||||
return (
|
||||
<span className="avatar" data-size={size}>
|
||||
{firstLettersArgs(original?.display_name)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
11
src/components/AvaterCell.tsx
Normal file
11
src/components/AvaterCell.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { firstLettersArgs } from '@/utils';
|
||||
|
||||
export default function AvatarCell({ row: { original }, size }) {
|
||||
return (
|
||||
<span className="avatar" data-size={size}>
|
||||
{firstLettersArgs(original?.display_name)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
import { Classes } from '@blueprintjs/core';
|
||||
import clsx from 'classnames';
|
||||
import Icon from '../Icon';
|
||||
|
||||
const ACCOUNT_TYPE = {
|
||||
CASH: 'cash',
|
||||
BANK: 'bank',
|
||||
CREDIT_CARD: 'credit-card',
|
||||
};
|
||||
|
||||
const ACCOUNT_TYPE_PAIR_ICON = {
|
||||
[ACCOUNT_TYPE.CASH]: 'payments',
|
||||
[ACCOUNT_TYPE.CREDIT_CARD]: 'credit-card',
|
||||
[ACCOUNT_TYPE.BANK]: 'account-balance',
|
||||
};
|
||||
|
||||
function BankAccountMetaLine({ title, value, className }) {
|
||||
return (
|
||||
<MetaLineWrap className={className}>
|
||||
<MetaLineTitle>{title}</MetaLineTitle>
|
||||
{value && <MetaLineValue>{value}</MetaLineValue>}
|
||||
</MetaLineWrap>
|
||||
);
|
||||
}
|
||||
|
||||
function BankAccountBalance({ amount, loading }) {
|
||||
return (
|
||||
<BankAccountBalanceWrap>
|
||||
<BankAccountBalanceAmount
|
||||
className={clsx({
|
||||
[Classes.SKELETON]: loading,
|
||||
})}
|
||||
>
|
||||
{amount}
|
||||
</BankAccountBalanceAmount>
|
||||
<BankAccountBalanceLabel>{intl.get('balance')}</BankAccountBalanceLabel>
|
||||
</BankAccountBalanceWrap>
|
||||
);
|
||||
}
|
||||
|
||||
function BankAccountTypeIcon({ type }) {
|
||||
const icon = ACCOUNT_TYPE_PAIR_ICON[type];
|
||||
|
||||
if (!icon) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<AccountIconWrap>
|
||||
<Icon icon={icon} iconSize={18} />
|
||||
</AccountIconWrap>
|
||||
);
|
||||
}
|
||||
|
||||
export function BankAccount({
|
||||
title,
|
||||
code,
|
||||
type,
|
||||
balance,
|
||||
loading = false,
|
||||
updatedBeforeText,
|
||||
...restProps
|
||||
}) {
|
||||
return (
|
||||
<BankAccountWrap {...restProps}>
|
||||
<BankAccountHeader>
|
||||
<BankAccountTitle className={clsx({ [Classes.SKELETON]: loading })}>
|
||||
{title}
|
||||
</BankAccountTitle>
|
||||
<BnakAccountCode className={clsx({ [Classes.SKELETON]: loading })}>
|
||||
{code}
|
||||
</BnakAccountCode>
|
||||
{!loading && <BankAccountTypeIcon type={type} />}
|
||||
</BankAccountHeader>
|
||||
|
||||
<BankAccountMeta>
|
||||
<BankAccountMetaLine
|
||||
title={intl.get('cash_flow.label_account_transcations')}
|
||||
value={2}
|
||||
className={clsx({ [Classes.SKELETON]: loading })}
|
||||
/>
|
||||
<BankAccountMetaLine
|
||||
title={updatedBeforeText}
|
||||
className={clsx({ [Classes.SKELETON]: loading })}
|
||||
/>
|
||||
</BankAccountMeta>
|
||||
|
||||
<BankAccountBalance amount={balance} loading={loading} />
|
||||
</BankAccountWrap>
|
||||
);
|
||||
}
|
||||
|
||||
const BankAccountWrap = styled.div`
|
||||
width: 225px;
|
||||
height: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
margin: 8px;
|
||||
border: 1px solid #c8cad0;
|
||||
transition: all 0.1s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
border-color: #0153cc;
|
||||
}
|
||||
`;
|
||||
|
||||
const BankAccountHeader = styled.div`
|
||||
padding: 10px 12px;
|
||||
padding-top: 16px;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const BankAccountTitle = styled.div`
|
||||
font-size: 15px;
|
||||
font-style: inherit;
|
||||
letter-spacing: -0.003em;
|
||||
color: rgb(23, 43, 77);
|
||||
white-space: nowrap;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0px;
|
||||
`;
|
||||
|
||||
const BnakAccountCode = styled.div`
|
||||
font-size: 11px;
|
||||
margin-top: 4px;
|
||||
color: rgb(23, 43, 77);
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
const BankAccountBalanceWrap = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: auto;
|
||||
border-top: 1px solid #dfdfdf;
|
||||
padding: 10px 12px;
|
||||
`;
|
||||
|
||||
const BankAccountBalanceAmount = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: #57657e;
|
||||
`;
|
||||
|
||||
const BankAccountBalanceLabel = styled.div`
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 3px;
|
||||
opacity: 0.6;
|
||||
`;
|
||||
|
||||
const MetaLineWrap = styled.div`
|
||||
font-size: 11px;
|
||||
display: flex;
|
||||
color: #2f3c58;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
margin-top: 6px;
|
||||
}
|
||||
`;
|
||||
const MetaLineTitle = styled.div``;
|
||||
|
||||
const MetaLineValue = styled.div`
|
||||
box-sizing: border-box;
|
||||
font-style: inherit;
|
||||
background: rgb(223, 225, 230);
|
||||
line-height: initial;
|
||||
align-content: center;
|
||||
padding: 0px 2px;
|
||||
border-radius: 9.6px;
|
||||
font-weight: normal;
|
||||
text-transform: none;
|
||||
width: 30px;
|
||||
min-width: 30px;
|
||||
height: 16px;
|
||||
text-align: center;
|
||||
color: rgb(23, 43, 77);
|
||||
font-size: 11px;
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
const BankAccountMeta = styled.div`
|
||||
padding: 0 12px 10px;
|
||||
`;
|
||||
|
||||
export const BankAccountsList = styled.div`
|
||||
display: flex;
|
||||
margin: -8px;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const AccountIconWrap = styled.div`
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
color: #abb3bb;
|
||||
right: 12px;
|
||||
`;
|
||||
206
src/components/BankAccounts/index.tsx
Normal file
206
src/components/BankAccounts/index.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
import clsx from 'classnames';
|
||||
import { Classes } from '@blueprintjs/core';
|
||||
import { Icon } from '@/components/Icon';
|
||||
|
||||
const ACCOUNT_TYPE = {
|
||||
CASH: 'cash',
|
||||
BANK: 'bank',
|
||||
CREDIT_CARD: 'credit-card',
|
||||
};
|
||||
|
||||
const ACCOUNT_TYPE_PAIR_ICON = {
|
||||
[ACCOUNT_TYPE.CASH]: 'payments',
|
||||
[ACCOUNT_TYPE.CREDIT_CARD]: 'credit-card',
|
||||
[ACCOUNT_TYPE.BANK]: 'account-balance',
|
||||
};
|
||||
|
||||
function BankAccountMetaLine({ title, value, className }) {
|
||||
return (
|
||||
<MetaLineWrap className={className}>
|
||||
<MetaLineTitle>{title}</MetaLineTitle>
|
||||
{value && <MetaLineValue>{value}</MetaLineValue>}
|
||||
</MetaLineWrap>
|
||||
);
|
||||
}
|
||||
|
||||
function BankAccountBalance({ amount, loading }) {
|
||||
return (
|
||||
<BankAccountBalanceWrap>
|
||||
<BankAccountBalanceAmount
|
||||
className={clsx({
|
||||
[Classes.SKELETON]: loading,
|
||||
})}
|
||||
>
|
||||
{amount}
|
||||
</BankAccountBalanceAmount>
|
||||
<BankAccountBalanceLabel>{intl.get('balance')}</BankAccountBalanceLabel>
|
||||
</BankAccountBalanceWrap>
|
||||
);
|
||||
}
|
||||
|
||||
function BankAccountTypeIcon({ type }) {
|
||||
const icon = ACCOUNT_TYPE_PAIR_ICON[type];
|
||||
|
||||
if (!icon) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<AccountIconWrap>
|
||||
<Icon icon={icon} iconSize={18} />
|
||||
</AccountIconWrap>
|
||||
);
|
||||
}
|
||||
|
||||
export function BankAccount({
|
||||
title,
|
||||
code,
|
||||
type,
|
||||
balance,
|
||||
loading = false,
|
||||
updatedBeforeText,
|
||||
...restProps
|
||||
}) {
|
||||
return (
|
||||
<BankAccountWrap {...restProps}>
|
||||
<BankAccountHeader>
|
||||
<BankAccountTitle className={clsx({ [Classes.SKELETON]: loading })}>
|
||||
{title}
|
||||
</BankAccountTitle>
|
||||
<BnakAccountCode className={clsx({ [Classes.SKELETON]: loading })}>
|
||||
{code}
|
||||
</BnakAccountCode>
|
||||
{!loading && <BankAccountTypeIcon type={type} />}
|
||||
</BankAccountHeader>
|
||||
|
||||
<BankAccountMeta>
|
||||
<BankAccountMetaLine
|
||||
title={intl.get('cash_flow.label_account_transcations')}
|
||||
value={2}
|
||||
className={clsx({ [Classes.SKELETON]: loading })}
|
||||
/>
|
||||
<BankAccountMetaLine
|
||||
title={updatedBeforeText}
|
||||
className={clsx({ [Classes.SKELETON]: loading })}
|
||||
/>
|
||||
</BankAccountMeta>
|
||||
|
||||
<BankAccountBalance amount={balance} loading={loading} />
|
||||
</BankAccountWrap>
|
||||
);
|
||||
}
|
||||
|
||||
const BankAccountWrap = styled.div`
|
||||
width: 225px;
|
||||
height: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
margin: 8px;
|
||||
border: 1px solid #c8cad0;
|
||||
transition: all 0.1s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
border-color: #0153cc;
|
||||
}
|
||||
`;
|
||||
|
||||
const BankAccountHeader = styled.div`
|
||||
padding: 10px 12px;
|
||||
padding-top: 16px;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const BankAccountTitle = styled.div`
|
||||
font-size: 15px;
|
||||
font-style: inherit;
|
||||
letter-spacing: -0.003em;
|
||||
color: rgb(23, 43, 77);
|
||||
white-space: nowrap;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0px;
|
||||
`;
|
||||
|
||||
const BnakAccountCode = styled.div`
|
||||
font-size: 11px;
|
||||
margin-top: 4px;
|
||||
color: rgb(23, 43, 77);
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
const BankAccountBalanceWrap = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: auto;
|
||||
border-top: 1px solid #dfdfdf;
|
||||
padding: 10px 12px;
|
||||
`;
|
||||
|
||||
const BankAccountBalanceAmount = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: #57657e;
|
||||
`;
|
||||
|
||||
const BankAccountBalanceLabel = styled.div`
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 3px;
|
||||
opacity: 0.6;
|
||||
`;
|
||||
|
||||
const MetaLineWrap = styled.div`
|
||||
font-size: 11px;
|
||||
display: flex;
|
||||
color: #2f3c58;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
margin-top: 6px;
|
||||
}
|
||||
`;
|
||||
const MetaLineTitle = styled.div``;
|
||||
|
||||
const MetaLineValue = styled.div`
|
||||
box-sizing: border-box;
|
||||
font-style: inherit;
|
||||
background: rgb(223, 225, 230);
|
||||
line-height: initial;
|
||||
align-content: center;
|
||||
padding: 0px 2px;
|
||||
border-radius: 9.6px;
|
||||
font-weight: normal;
|
||||
text-transform: none;
|
||||
width: 30px;
|
||||
min-width: 30px;
|
||||
height: 16px;
|
||||
text-align: center;
|
||||
color: rgb(23, 43, 77);
|
||||
font-size: 11px;
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
const BankAccountMeta = styled.div`
|
||||
padding: 0 12px 10px;
|
||||
`;
|
||||
|
||||
export const BankAccountsList = styled.div`
|
||||
display: flex;
|
||||
margin: -8px;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const AccountIconWrap = styled.div`
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
color: #abb3bb;
|
||||
right: 12px;
|
||||
`;
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
import styled from 'styled-components';
|
||||
import { CurrencyTag } from 'components';
|
||||
|
||||
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||
|
||||
/**
|
||||
* base currecncy.
|
||||
*/
|
||||
function BaseCurrency({
|
||||
// #withCurrentOrganization
|
||||
organization: { base_currency },
|
||||
}) {
|
||||
return <CurrencyTag>{base_currency}</CurrencyTag>;
|
||||
}
|
||||
|
||||
export default R.compose(withCurrentOrganization())(BaseCurrency);
|
||||
@@ -1,124 +0,0 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { Suggest } from '@blueprintjs/select';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
/**
|
||||
* branch suggest field.
|
||||
* @returns
|
||||
*/
|
||||
export default function BranchSuggestField({
|
||||
branches,
|
||||
initialBranchId,
|
||||
selectedBranchId,
|
||||
defaultSelectText = intl.get('select_branch'),
|
||||
popoverFill = false,
|
||||
onBranchSelected,
|
||||
...suggestProps
|
||||
}) {
|
||||
const initialBranch = React.useMemo(
|
||||
() => branches.find((b) => b.id === initialBranchId),
|
||||
[initialBranchId, branches],
|
||||
);
|
||||
|
||||
const [selectedBranch, setSelectedBranch] = React.useState(
|
||||
initialBranch || null,
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof selectedBranchId !== 'undefined') {
|
||||
const branch = selectedBranchId
|
||||
? branches.find((a) => a.id === selectedBranchId)
|
||||
: null;
|
||||
setSelectedBranch(branch);
|
||||
}
|
||||
}, [selectedBranchId, branches, setSelectedBranch]);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} branch
|
||||
* @returns
|
||||
*/
|
||||
const branchItemRenderer = (branch, { handleClick, modifiers, query }) => {
|
||||
return (
|
||||
<MenuItem
|
||||
// active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
label={branch.code}
|
||||
key={branch.id}
|
||||
onClick={handleClick}
|
||||
text={branch.name}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} branch
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const branchItemPredicate = (query, branch, _index, exactMatch) => {
|
||||
const normalizedTitle = branch.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${branch.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} branch
|
||||
* @returns
|
||||
*/
|
||||
const brnachItemSelect = React.useCallback(
|
||||
(branch) => {
|
||||
if (branch.id) {
|
||||
setSelectedBranch({ ...branch });
|
||||
onBranchSelected && onBranchSelected(branch);
|
||||
}
|
||||
},
|
||||
[setSelectedBranch, onBranchSelected],
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} inputVaue
|
||||
* @returns
|
||||
*/
|
||||
const branchInputValueRenderer = (inputValue) => {
|
||||
if (inputValue) {
|
||||
return inputValue.name.toString();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
return (
|
||||
<Suggest
|
||||
items={branches}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
||||
itemRenderer={branchItemRenderer}
|
||||
itemPredicate={branchItemPredicate}
|
||||
onItemSelect={brnachItemSelect}
|
||||
selectedItem={selectedBranch}
|
||||
inputProps={{ placeholder: defaultSelectText }}
|
||||
resetOnClose={true}
|
||||
fill={true}
|
||||
popoverProps={{ minimal: true, boundary: 'window' }}
|
||||
inputValueRenderer={branchInputValueRenderer}
|
||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
{...suggestProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { FMultiSelect } from '../Forms';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} branch
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const branchItemPredicate = (query, branch, _index, exactMatch) => {
|
||||
const normalizedTitle = branch.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${branch.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} branch
|
||||
* @param {*} param1
|
||||
* @returns
|
||||
*/
|
||||
const branchItemRenderer = (
|
||||
branch,
|
||||
{ handleClick, modifiers, query },
|
||||
{ isSelected },
|
||||
) => {
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
icon={isSelected ? 'tick' : 'blank'}
|
||||
text={branch.name}
|
||||
label={branch.code}
|
||||
key={branch.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const branchSelectProps = {
|
||||
itemPredicate: branchItemPredicate,
|
||||
itemRenderer: branchItemRenderer,
|
||||
valueAccessor: (item) => item.id,
|
||||
labelAccessor: (item) => item.code,
|
||||
tagRenderer: (item) => item.name,
|
||||
};
|
||||
|
||||
/**
|
||||
* branches mulit select.
|
||||
* @param {*} param0
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function BranchMultiSelect({ branches, ...rest }) {
|
||||
return (
|
||||
<FMultiSelect
|
||||
items={branches}
|
||||
placeholder={intl.get('branches_multi_select.placeholder')}
|
||||
popoverProps={{ minimal: true }}
|
||||
{...branchSelectProps}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
73
src/components/Branches/BranchMultiSelect.tsx
Normal file
73
src/components/Branches/BranchMultiSelect.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { FMultiSelect } from '../Forms';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} branch
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const branchItemPredicate = (query, branch, _index, exactMatch) => {
|
||||
const normalizedTitle = branch.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${branch.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} branch
|
||||
* @param {*} param1
|
||||
* @returns
|
||||
*/
|
||||
const branchItemRenderer = (
|
||||
branch,
|
||||
{ handleClick, modifiers, query },
|
||||
{ isSelected },
|
||||
) => {
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
icon={isSelected ? 'tick' : 'blank'}
|
||||
text={branch.name}
|
||||
label={branch.code}
|
||||
key={branch.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const branchSelectProps = {
|
||||
itemPredicate: branchItemPredicate,
|
||||
itemRenderer: branchItemRenderer,
|
||||
valueAccessor: (item) => item.id,
|
||||
labelAccessor: (item) => item.code,
|
||||
tagRenderer: (item) => item.name,
|
||||
};
|
||||
|
||||
/**
|
||||
* branches mulit select.
|
||||
* @param {*} param0
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function BranchMultiSelect({ branches, ...rest }) {
|
||||
return (
|
||||
<FMultiSelect
|
||||
items={branches}
|
||||
placeholder={intl.get('branches_multi_select.placeholder')}
|
||||
popoverProps={{ minimal: true }}
|
||||
{...branchSelectProps}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { MenuItem, Button } from '@blueprintjs/core';
|
||||
import { FSelect } from '../Forms';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} branch
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const branchItemPredicate = (query, branch, _index, exactMatch) => {
|
||||
const normalizedTitle = branch.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${branch.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} film
|
||||
* @param {*} param1
|
||||
* @returns
|
||||
*/
|
||||
const branchItemRenderer = (branch, { handleClick, modifiers, query }) => {
|
||||
const text = `${branch.name}`;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
label={branch.code}
|
||||
key={branch.id}
|
||||
onClick={handleClick}
|
||||
text={text}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const branchSelectProps = {
|
||||
itemPredicate: branchItemPredicate,
|
||||
itemRenderer: branchItemRenderer,
|
||||
valueAccessor: 'id',
|
||||
labelAccessor: 'name',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export function BranchSelect({ branches, ...rest }) {
|
||||
return <FSelect {...branchSelectProps} {...rest} items={branches} />;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export function BranchSelectButton({ label, ...rest }) {
|
||||
return <Button text={label} {...rest} />;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user