Compare commits
277 Commits
BIG-336-it
...
BIG-386-li
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
754618aa7a | ||
|
|
50c905eabb | ||
|
|
709e06a646 | ||
|
|
8826d2bc5b | ||
|
|
38a961b899 | ||
|
|
7ef7e126e5 | ||
|
|
bcf0ec25b8 | ||
|
|
965a8966f6 | ||
|
|
b030d6ea37 | ||
|
|
31fef21362 | ||
|
|
6f2a456a56 | ||
|
|
6134ad5598 | ||
|
|
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 | ||
|
|
01038136f2 | ||
|
|
1172e69d96 | ||
|
|
87758bf773 | ||
|
|
5cbb3c84e6 | ||
|
|
52924383bd | ||
|
|
8d1825a065 | ||
|
|
5e4e9c37c3 | ||
|
|
944bc29f4d | ||
|
|
682b296f7c | ||
|
|
e662bf7af9 | ||
|
|
a829ceb709 | ||
|
|
cdce00187b | ||
|
|
91a38b34cc | ||
|
|
1b97a162e8 | ||
|
|
e8e12e63ea | ||
|
|
80feba6005 | ||
|
|
cc457e1e43 | ||
|
|
2ced5dc013 | ||
|
|
dd86c2993e | ||
|
|
bd05a4a188 | ||
|
|
8160cbe402 | ||
|
|
0ef6bebfb8 | ||
|
|
91ff3fdccb | ||
|
|
edd37fff78 | ||
|
|
679f7ce96c | ||
|
|
79b3ab9ec7 | ||
|
|
e7158b7ba7 | ||
|
|
569bc1c4a4 | ||
|
|
b880732087 | ||
|
|
b1e7720bd9 | ||
|
|
5eb9968095 | ||
|
|
b0cf8f723f | ||
|
|
b0a826e62a | ||
|
|
6daa9f09a5 | ||
|
|
f828d85880 | ||
|
|
46d895bef9 | ||
|
|
1fa26c7cb7 | ||
|
|
41d2fc63cb | ||
|
|
4814a40fa9 | ||
|
|
b903aa3eb2 | ||
|
|
872b98fb0d | ||
|
|
b849bfaa95 | ||
|
|
756af3c4d3 | ||
|
|
b935d13918 | ||
|
|
4aea9cb19b | ||
|
|
d22212e6e3 | ||
|
|
6a6ff16c48 | ||
|
|
d08894820d | ||
|
|
2db32b8ee8 | ||
|
|
86c6de361b | ||
|
|
9bd13b0d46 | ||
|
|
30b17d697f | ||
|
|
ce674466fe | ||
|
|
373a695c4c | ||
|
|
3f4ffdc995 | ||
|
|
a9c2a5c5f0 | ||
|
|
908d232cb9 | ||
|
|
10af33f1dc | ||
|
|
767d807490 | ||
|
|
ba1d945dca | ||
|
|
03ea8643e7 | ||
|
|
3d78e5d397 | ||
|
|
fffd255eb1 | ||
|
|
d0f09a0164 | ||
|
|
7774d9f5ab | ||
|
|
e5ab240dfd | ||
|
|
2b07917399 | ||
|
|
79099e1abc | ||
|
|
e87b22801b | ||
|
|
e0eaa56b5c | ||
|
|
d842722183 | ||
|
|
9c14f10edf | ||
|
|
6a8137729f | ||
|
|
5c601fcf2d | ||
|
|
df4c4a832b | ||
|
|
86cab7988c | ||
|
|
8404fee10a | ||
|
|
99a23889bc | ||
|
|
5e2000d252 | ||
|
|
73cb0ec32e | ||
|
|
8fc11b3237 | ||
|
|
eecb81e882 | ||
|
|
2e743f2232 | ||
|
|
3faa765a07 | ||
|
|
a6d93170df | ||
|
|
3d9b95cbcf | ||
|
|
4882afd63f | ||
|
|
8dac7e3d98 | ||
|
|
6e6b005fc9 | ||
|
|
68323486e2 | ||
|
|
fd357196a8 | ||
|
|
5a508dd789 | ||
|
|
bac6812d36 | ||
|
|
ba71679b55 | ||
|
|
bc0f4f4cd9 | ||
|
|
4e53b3a2d5 | ||
|
|
a913b84723 | ||
|
|
50f1979c9f | ||
|
|
3722afdc47 | ||
|
|
5711fb8366 | ||
|
|
09f4d0fbe0 | ||
|
|
918780bf4c | ||
|
|
8b78fbdb86 | ||
|
|
2c0291da84 | ||
|
|
a17fafbc90 | ||
|
|
701c32debd | ||
|
|
70e1e94a4a | ||
|
|
90c77d80eb | ||
|
|
932afddf11 | ||
|
|
a533c3cb76 | ||
|
|
2f36594459 | ||
|
|
a093c0d335 | ||
|
|
120f8d15ec | ||
|
|
65bb05d498 | ||
|
|
64d73fa7b9 | ||
|
|
928e903fe0 | ||
|
|
23261e975d | ||
|
|
e51f203ca8 | ||
|
|
35f40503f3 | ||
|
|
f86845ea91 | ||
|
|
29fbcf1f1c | ||
|
|
39a68f5c25 | ||
|
|
89b28903fa | ||
|
|
4f7e9caedb | ||
|
|
ef91afe041 | ||
|
|
dd5e10ef83 | ||
|
|
acdec70385 | ||
|
|
05126253db | ||
|
|
cc637471d9 | ||
|
|
321d206670 | ||
|
|
69c47aee4d | ||
|
|
ad149c1b18 | ||
|
|
7df4cbdf54 | ||
|
|
ca0672509b | ||
|
|
966d1100aa | ||
|
|
0e263437e1 | ||
|
|
e1977dbe07 | ||
|
|
e6d7d6aa1c | ||
|
|
8e7955bc7e | ||
|
|
379d033344 | ||
|
|
378110f6b3 | ||
|
|
888c3a11e0 | ||
|
|
b0407168a0 | ||
|
|
7f3a494c8d | ||
|
|
d85133b35e | ||
|
|
a360f8a62b | ||
|
|
a91f303111 | ||
|
|
f45d5ecf42 | ||
|
|
3d49ebd1e7 | ||
|
|
f84023f399 | ||
|
|
dd46180d87 | ||
|
|
3e91b01b61 | ||
|
|
bd3059ecd8 | ||
|
|
0bbd31dcce | ||
|
|
c5c490d7ce | ||
|
|
733f198dcb | ||
|
|
c17234f245 | ||
|
|
a2bf37d5cd | ||
|
|
a75451cee7 | ||
|
|
c1ad349f6b | ||
|
|
37f8662cc5 | ||
|
|
c5e360ffa2 | ||
|
|
098429d31a | ||
|
|
433b611188 | ||
|
|
6638accae6 | ||
|
|
2d34baa7a5 | ||
|
|
0f4176ae79 | ||
|
|
4f630d8365 | ||
|
|
e38b96c528 | ||
|
|
e03dee2a9f | ||
|
|
a8311f1798 | ||
|
|
b8b95c7929 | ||
|
|
2501626d70 | ||
|
|
87a56dcd95 | ||
|
|
b26b4c5491 | ||
|
|
d12e35f649 | ||
|
|
51c7a0fcd8 | ||
|
|
a639101e28 | ||
|
|
7467b63e54 | ||
|
|
5c52f80536 | ||
|
|
94c88a7003 | ||
|
|
7f4ee26979 | ||
|
|
e46609e3f1 | ||
|
|
f814374279 | ||
|
|
cfbc59280e | ||
|
|
0923c69c16 | ||
|
|
151e72a76d | ||
|
|
ff2c74344a | ||
|
|
eb340269c0 | ||
|
|
cb9c7fcdb6 | ||
|
|
31a5ee6dda | ||
|
|
01db5a2faa | ||
|
|
a3c79d98b0 | ||
|
|
deddbea752 | ||
|
|
e0126018b8 | ||
|
|
22eb7a1cc1 | ||
|
|
e6a7c7bc58 | ||
|
|
cf7d032aae | ||
|
|
faea111f46 | ||
|
|
836be59cbf | ||
|
|
ffd6629b80 | ||
|
|
9ff91e27e5 | ||
|
|
b15437c88e | ||
|
|
7c9ad8438c | ||
|
|
98a02396a9 | ||
|
|
c7673f57cd | ||
|
|
aa39aab93a | ||
|
|
8e6b0b496f | ||
|
|
914e1de79f | ||
|
|
f6f949bcbc | ||
|
|
f7790ef440 | ||
|
|
05b0f5caac | ||
|
|
f5983937b0 | ||
|
|
3a4c1adbd4 | ||
|
|
e9d379c623 | ||
|
|
b46c3f4843 | ||
|
|
96635ffa84 | ||
|
|
e874b89d2d | ||
|
|
60d37e3424 | ||
|
|
8ae39bf04c | ||
|
|
e3f2c82a38 | ||
|
|
68c0678dc3 | ||
|
|
030be9652c | ||
|
|
554527f17d | ||
|
|
79144ad4a5 | ||
|
|
e6fcbfeea6 | ||
|
|
7eacaa0660 | ||
|
|
673808cceb | ||
|
|
f27ef2c9b0 | ||
|
|
da699a766a | ||
|
|
f1899e1ce1 | ||
|
|
11851d114d | ||
|
|
21779007be | ||
|
|
4fc1ecdc2d | ||
|
|
c9b5cecf7a | ||
|
|
c31e9dcd29 | ||
|
|
430ab95dc3 | ||
|
|
8100a57195 |
79
CHANGELOG.md
@@ -2,6 +2,85 @@
|
||||
|
||||
All notable changes to Bigcapital server-side will be in this file.
|
||||
|
||||
## [1.7.4-rc.2] - 20-04-2022
|
||||
|
||||
### Fixed
|
||||
- `BIG-374` Refactoring sidebar men with ability permissions and feature control on each item.
|
||||
|
||||
## [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.
|
||||
|
||||
## [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.
|
||||
## 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.
|
||||
|
||||
## [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.
|
||||
|
||||
## 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.
|
||||
|
||||
## [1.6.3] - 21-02-2022
|
||||
|
||||
### Fixed
|
||||
- `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.
|
||||
|
||||
## [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.
|
||||
- Profit & loss sheet comparison of preivous period (PP).
|
||||
- Profit & loss sheet comparison of previous year (PY).
|
||||
- Profit & loss sheet percentage analysis columns, rows, income and expenses basis.
|
||||
|
||||
## [1.5.8] - 13-01-2022
|
||||
|
||||
### Added
|
||||
|
||||
@@ -85,6 +85,9 @@ function getClientEnvironment(publicUrl) {
|
||||
WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST,
|
||||
WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
|
||||
WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
|
||||
|
||||
// Application version.
|
||||
VERSION: paths.appVersion
|
||||
}
|
||||
);
|
||||
// Stringify all values so we can feed into webpack DefinePlugin
|
||||
|
||||
@@ -48,6 +48,8 @@ const resolveModule = (resolveFn, filePath) => {
|
||||
return resolveFn(`${filePath}.js`);
|
||||
};
|
||||
|
||||
const appVersion = require(resolveApp('package.json')).version;
|
||||
|
||||
// config after eject: we're in ./config/
|
||||
module.exports = {
|
||||
dotenv: resolveApp('.env'),
|
||||
@@ -65,6 +67,7 @@ module.exports = {
|
||||
proxySetup: resolveApp('src/setupProxy.js'),
|
||||
appNodeModules: resolveApp('node_modules'),
|
||||
publicUrlOrPath,
|
||||
appVersion
|
||||
};
|
||||
|
||||
|
||||
|
||||
42
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bigcapital-client",
|
||||
"version": "1.5.8",
|
||||
"version": "1.7.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1043,17 +1043,6 @@
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@blueprintjs-formik/core": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/core/-/core-0.1.5.tgz",
|
||||
"integrity": "sha512-H0aXiNMYC8RwhWR1F2O77dcRcRITijUX5we51G4AK2Vmp1yXCmNb0piN9ftsjL5vVIvIsMKWg+dfbwREmB5VWg==",
|
||||
"requires": {
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.keyby": "^4.6.0",
|
||||
"styled-components": "^5.3.3",
|
||||
"web-vitals": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"@blueprintjs-formik/select": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/select/-/select-0.1.4.tgz",
|
||||
@@ -1980,6 +1969,16 @@
|
||||
"integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@types/http-proxy": {
|
||||
"version": "1.17.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.3.tgz",
|
||||
@@ -2240,6 +2239,25 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
|
||||
},
|
||||
"@types/styled-components": {
|
||||
"version": "5.1.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.25.tgz",
|
||||
"integrity": "sha512-fgwl+0Pa8pdkwXRoVPP9JbqF0Ivo9llnmsm+7TCI330kbPIFd9qv1Lrhr37shf4tnxCOSu+/IgqM7uJXLWZZNQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/hoist-non-react-statics": "*",
|
||||
"@types/react": "*",
|
||||
"csstype": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"csstype": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
|
||||
"integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/testing-library__dom": {
|
||||
"version": "6.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.12.1.tgz",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "bigcapital-client",
|
||||
"version": "1.5.8",
|
||||
"version": "1.7.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7.8.4",
|
||||
"@blueprintjs-formik/core": "^0.1.5",
|
||||
"@blueprintjs-formik/core": "^0.2.1",
|
||||
"@blueprintjs-formik/select": "^0.1.4",
|
||||
"@blueprintjs/core": "^3.50.2",
|
||||
"@blueprintjs/datetime": "^3.23.12",
|
||||
@@ -38,6 +38,7 @@
|
||||
"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",
|
||||
@@ -150,6 +151,7 @@
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
"@types/styled-components": "^5.1.25",
|
||||
"@types/yup": "^0.29.13",
|
||||
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
|
||||
"compression-webpack-plugin": "^6.1.0",
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -221,3 +221,5 @@ export const ACCOUNT_TYPES = [
|
||||
incomeSheet: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const FOREIGN_CURRENCY_ACCOUNTS = ['cash', 'bank'];
|
||||
|
||||
7
src/common/cellTypes.js
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
export const CellType = {
|
||||
Text: 'text',
|
||||
Field: 'field',
|
||||
Button: 'button'
|
||||
}
|
||||
@@ -2,5 +2,6 @@
|
||||
|
||||
export const Features = {
|
||||
Warehouses: 'warehouses',
|
||||
Branches: 'branches'
|
||||
Branches: 'branches',
|
||||
ManualJournal: 'manualJournal',
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
export * from './accountTypes';
|
||||
export * from './TableStyle';
|
||||
export * from './features';
|
||||
export * from './cellTypes';
|
||||
|
||||
export const Align = { Left: 'left', Right: 'right', Center: 'center' };
|
||||
|
||||
@@ -16,7 +16,11 @@ export const TABLES = {
|
||||
CASHFLOW_Transactions: 'cashflow_transactions',
|
||||
CREDIT_NOTES: 'credit_notes',
|
||||
VENDOR_CREDITS: 'vendor_credits',
|
||||
WAREHOUSE_TRANSFERS:'warehouse_transfers'
|
||||
WAREHOUSE_TRANSFERS: 'warehouse_transfers',
|
||||
PROJECTS: 'projects',
|
||||
TIMESHEETS: 'timesheets',
|
||||
PURCHASES: 'purchases',
|
||||
SALES: 'sales',
|
||||
};
|
||||
|
||||
export const TABLE_SIZE = {
|
||||
|
||||
73
src/components/Accounts/AccountMultiSelect.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { FMultiSelect } from '../Forms';
|
||||
import classNames from 'classnames';
|
||||
import { Classes } from '@blueprintjs/popover2';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} account
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const accountItemPredicate = (query, account, _index, exactMatch) => {
|
||||
const normalizedTitle = account.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${account.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} account
|
||||
* @param {*} param1
|
||||
* @returns
|
||||
*/
|
||||
const accountItemRenderer = (
|
||||
account,
|
||||
{ handleClick, modifiers, query },
|
||||
{ isSelected },
|
||||
) => {
|
||||
return (
|
||||
<MenuItem
|
||||
icon={isSelected ? 'tick' : 'blank'}
|
||||
text={account.name}
|
||||
label={account.code}
|
||||
key={account.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const accountSelectProps = {
|
||||
itemPredicate: accountItemPredicate,
|
||||
itemRenderer: accountItemRenderer,
|
||||
valueAccessor: (item) => item.id,
|
||||
labelAccessor: (item) => item.code,
|
||||
tagRenderer: (item) => item.name,
|
||||
};
|
||||
|
||||
/**
|
||||
* branches mulit select.
|
||||
* @param {*} param0
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function AccountMultiSelect({ accounts, ...rest }) {
|
||||
return (
|
||||
<FMultiSelect
|
||||
items={accounts}
|
||||
popoverProps={{
|
||||
minimal: true,
|
||||
}}
|
||||
{...accountSelectProps}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
1
src/components/Accounts/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from './AccountMultiSelect';
|
||||
124
src/components/BranchSuggestField.js
Normal file
@@ -0,0 +1,124 @@
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -38,8 +38,8 @@ const branchItemRenderer = (
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
icon={isSelected ? 'tick' : 'blank'}
|
||||
text={branch.name.toString()}
|
||||
label={branch.code.toString()}
|
||||
text={branch.name}
|
||||
label={branch.code}
|
||||
key={branch.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
@@ -50,14 +50,14 @@ const branchSelectProps = {
|
||||
itemPredicate: branchItemPredicate,
|
||||
itemRenderer: branchItemRenderer,
|
||||
valueAccessor: (item) => item.id,
|
||||
labelAccessor: (item) => item.label,
|
||||
labelAccessor: (item) => item.code,
|
||||
tagRenderer: (item) => item.name,
|
||||
};
|
||||
|
||||
/**
|
||||
* branches mulit select.
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function BranchMultiSelect({ branches, ...rest }) {
|
||||
return (
|
||||
|
||||
@@ -35,7 +35,7 @@ const branchItemRenderer = (branch, { handleClick, modifiers, query }) => {
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
label={branch.name.toString()}
|
||||
label={branch.code}
|
||||
key={branch.id}
|
||||
onClick={handleClick}
|
||||
text={text}
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
|
||||
|
||||
export * from './ButtonLink';
|
||||
@@ -11,7 +11,7 @@ export const CommercialDocHeader = styled.div`
|
||||
`;
|
||||
|
||||
export const CommercialDocTopHeader = styled.div`
|
||||
margin-bottom: 25px;
|
||||
margin-bottom: 30px;
|
||||
`;
|
||||
|
||||
export const CommercialDocEntriesTable = styled(DataTable)`
|
||||
|
||||
10
src/components/Currencies/BaseCurrency.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { CurrencyTag } from 'components';
|
||||
|
||||
/**
|
||||
* base currecncy.
|
||||
* @returns
|
||||
*/
|
||||
export function BaseCurrency({ currency }) {
|
||||
return <CurrencyTag>{currency}</CurrencyTag>;
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './CurrencySelect'
|
||||
export * from './CurrencySelect';
|
||||
export * from './BaseCurrency';
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { ButtonLink } from 'components';
|
||||
import { ButtonLink } from '../Button';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
function CustomerDrawerLinkComponent({
|
||||
// #ownProps
|
||||
children,
|
||||
customerId,
|
||||
className,
|
||||
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
}) {
|
||||
// Handle view customer drawer.
|
||||
const handleCustomerDrawer = () => {
|
||||
const handleCustomerDrawer = (event) => {
|
||||
openDrawer('customer-details-drawer', { customerId });
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
return <ButtonLink onClick={handleCustomerDrawer}>{children}</ButtonLink>;
|
||||
return (
|
||||
<ButtonLink className={className} onClick={handleCustomerDrawer}>
|
||||
{children}
|
||||
</ButtonLink>
|
||||
);
|
||||
}
|
||||
|
||||
export const CustomerDrawerLink = R.compose(withDrawerActions)(
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Switch, Route } from 'react-router';
|
||||
|
||||
import 'style/pages/Dashboard/Dashboard.scss';
|
||||
|
||||
import Sidebar from 'components/Sidebar/Sidebar';
|
||||
import { Sidebar } from 'containers/Dashboard/Sidebar/Sidebar';
|
||||
import DashboardContent from 'components/Dashboard/DashboardContent';
|
||||
import DialogsContainer from 'components/DialogsContainer';
|
||||
import PreferencesPage from 'components/Preferences/PreferencesPage';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Ability } from '@casl/ability';
|
||||
import { createContextualCan } from '@casl/react';
|
||||
import { useDashboardMeta } from '../../hooks/query';
|
||||
|
||||
import { useDashboardMetaBoot } from './DashboardBoot';
|
||||
|
||||
export const AbilityContext = React.createContext();
|
||||
export const Can = createContextualCan(AbilityContext.Consumer);
|
||||
@@ -11,8 +12,8 @@ export const Can = createContextualCan(AbilityContext.Consumer);
|
||||
*/
|
||||
export function DashboardAbilityProvider({ children }) {
|
||||
const {
|
||||
data: { abilities },
|
||||
} = useDashboardMeta();
|
||||
meta: { abilities },
|
||||
} = useDashboardMetaBoot();
|
||||
|
||||
// Ability instance.
|
||||
const ability = new Ability(abilities);
|
||||
|
||||
@@ -6,18 +6,26 @@ import {
|
||||
} from '../../hooks/query';
|
||||
import { useSplashLoading } from '../../hooks/state';
|
||||
import { useWatch, useWatchImmediate, useWhen } from '../../hooks';
|
||||
import { useSubscription } from '../../hooks/state';
|
||||
import { setCookie, getCookie } from '../../utils';
|
||||
|
||||
/**
|
||||
* Dashboard meta async booting.
|
||||
* - Fetches the dashboard meta only if the organization subscribe is active.
|
||||
* - Once the dashboard meta query is loading display dashboard splash screen.
|
||||
*/
|
||||
export function useDashboardMetaBoot() {
|
||||
const { isSubscriptionActive } = useSubscription();
|
||||
|
||||
const {
|
||||
data: dashboardMeta,
|
||||
isLoading: isDashboardMetaLoading,
|
||||
isSuccess: isDashboardMetaSuccess,
|
||||
} = useDashboardMeta({
|
||||
keepPreviousData: true,
|
||||
|
||||
// Avoid run the query if the organization subscription is not active.
|
||||
enabled: isSubscriptionActive,
|
||||
});
|
||||
const [startLoading, stopLoading] = useSplashLoading();
|
||||
|
||||
@@ -30,20 +38,12 @@ export function useDashboardMetaBoot() {
|
||||
}, isDashboardMetaSuccess);
|
||||
|
||||
return {
|
||||
meta: dashboardMeta,
|
||||
isLoading: isDashboardMetaLoading,
|
||||
isSuccess: isDashboardMetaSuccess
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dashboard async booting.
|
||||
* @returns {{ isLoading: boolean }}
|
||||
*/
|
||||
export function useDashboardBoot() {
|
||||
const { isLoading } = useDashboardMetaBoot();
|
||||
|
||||
return { isLoading };
|
||||
}
|
||||
|
||||
/**
|
||||
* Application async booting.
|
||||
*/
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { DashboardAbilityProvider } from '../../components';
|
||||
import { useDashboardBoot } from './DashboardBoot';
|
||||
import { useDashboardMetaBoot } from './DashboardBoot';
|
||||
|
||||
/**
|
||||
* Dashboard provider.
|
||||
*/
|
||||
export default function DashboardProvider({ children }) {
|
||||
const { isLoading } = useDashboardBoot();
|
||||
const { isLoading } = useDashboardMetaBoot();
|
||||
|
||||
// Avoid display any dashboard component before complete booting.
|
||||
if (isLoading) {
|
||||
|
||||
@@ -197,6 +197,7 @@ export default function DataTable(props) {
|
||||
DataTable.defaultProps = {
|
||||
pagination: false,
|
||||
hidePaginationNoPages: true,
|
||||
hideTableHeader: false,
|
||||
|
||||
size: null,
|
||||
spinnerProps: { size: 30 },
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import React, { useRef, useCallback, useMemo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useCellAutoFocus } from 'hooks';
|
||||
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { CellType } from 'common';
|
||||
import { useCellAutoFocus } from 'hooks';
|
||||
import AccountsSuggestField from 'components/AccountsSuggestField';
|
||||
|
||||
// import AccountsSelectList from 'components/AccountsSelectList';
|
||||
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
||||
|
||||
/**
|
||||
* Account cell renderer.
|
||||
*/
|
||||
@@ -74,3 +73,4 @@ export default function AccountCellRenderer({
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
AccountCellRenderer.cellType = CellType.Field;
|
||||
|
||||
44
src/components/DataTableCells/BranchesListFieldCell.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { FormGroup, Intent, Classes } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { CellType } from 'common';
|
||||
import BranchSuggestField from '../BranchSuggestField';
|
||||
|
||||
/**
|
||||
* Branches list field cell.
|
||||
* @returns
|
||||
*/
|
||||
export default function BranchesListFieldCell({
|
||||
column: { id },
|
||||
row: { index, original },
|
||||
payload: { branches, updateData, errors },
|
||||
}) {
|
||||
const handleBranchSelected = React.useCallback(
|
||||
(branch) => {
|
||||
updateData(index, 'branch_id', branch.id);
|
||||
},
|
||||
[updateData, index],
|
||||
);
|
||||
|
||||
const error = errors?.[index]?.[id];
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
intent={error ? Intent.DANGER : null}
|
||||
className={classNames(
|
||||
'form-group--select-list',
|
||||
'form-group--contacts-list',
|
||||
Classes.FILL,
|
||||
)}
|
||||
>
|
||||
<BranchSuggestField
|
||||
branches={branches}
|
||||
onBranchSelected={handleBranchSelected}
|
||||
selectedBranchId={original?.branch_id}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
BranchesListFieldCell.cellType = CellType.Field;
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { Classes, Checkbox, FormGroup, Intent } from '@blueprintjs/core';
|
||||
import { CellType } from 'common';
|
||||
|
||||
const CheckboxEditableCell = ({
|
||||
row: { index, original },
|
||||
@@ -45,4 +46,6 @@ const CheckboxEditableCell = ({
|
||||
);
|
||||
};
|
||||
|
||||
CheckboxEditableCell.cellType = CellType.Field;
|
||||
|
||||
export default CheckboxEditableCell;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { FormGroup, Intent, Classes } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import { ContactSelecetList } from 'components';
|
||||
import ContactsSuggestField from 'components/ContactsSuggestField';
|
||||
|
||||
import { CellType } from 'common';
|
||||
import ContactsSuggestField from 'components/ContactsSuggestField';
|
||||
export default function ContactsListCellRenderer({
|
||||
column: { id },
|
||||
row: { index, original },
|
||||
@@ -37,3 +37,5 @@ export default function ContactsListCellRenderer({
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
ContactsListCellRenderer.cellType = CellType.Field;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Classes, InputGroup, FormGroup, Intent } from '@blueprintjs/core';
|
||||
import { CellType } from 'common';
|
||||
|
||||
const InputEditableCell = ({
|
||||
row: { index },
|
||||
@@ -37,4 +38,6 @@ const InputEditableCell = ({
|
||||
);
|
||||
};
|
||||
|
||||
InputEditableCell.cellType = CellType.Field;
|
||||
|
||||
export default InputEditableCell;
|
||||
|
||||
@@ -3,6 +3,7 @@ import classNames from 'classnames';
|
||||
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { CellType } from 'common';
|
||||
import ItemsSuggestField from 'components/ItemsSuggestField';
|
||||
|
||||
import { useCellAutoFocus } from 'hooks';
|
||||
@@ -54,3 +55,5 @@ export default function ItemsListCell({
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
ItemsListCell.cellType = CellType.Field;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import { FormGroup, Intent } from '@blueprintjs/core';
|
||||
|
||||
import { MoneyInputGroup } from 'components';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { CellType } from 'common';
|
||||
|
||||
// Input form cell renderer.
|
||||
const MoneyFieldCellRenderer = ({
|
||||
@@ -48,4 +50,6 @@ const MoneyFieldCellRenderer = ({
|
||||
);
|
||||
};
|
||||
|
||||
MoneyFieldCellRenderer.cellType = CellType.Field;
|
||||
|
||||
export default MoneyFieldCellRenderer;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { FormGroup, NumericInput, Intent } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { CellType } from 'common';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
/**
|
||||
@@ -36,8 +38,10 @@ export default function NumericInputCell({
|
||||
onValueChange={handleValueChange}
|
||||
onBlur={onBlur}
|
||||
fill={true}
|
||||
buttonPosition={"none"}
|
||||
buttonPosition={'none'}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
NumericInputCell.cellType = CellType.Field;
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import PaymentReceiveListField from 'components/PaymentReceiveListField';
|
||||
import classNames from 'classnames';
|
||||
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
||||
|
||||
import PaymentReceiveListField from 'components/PaymentReceiveListField';
|
||||
import { CellType } from 'common';
|
||||
function PaymentReceiveListFieldCell({
|
||||
column: { id },
|
||||
row: { index },
|
||||
@@ -32,4 +33,6 @@ function PaymentReceiveListFieldCell({
|
||||
);
|
||||
}
|
||||
|
||||
PaymentReceiveListFieldCell.cellType = CellType.Field;
|
||||
|
||||
export default PaymentReceiveListFieldCell;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import { FormGroup, Intent } from '@blueprintjs/core';
|
||||
|
||||
import { MoneyInputGroup } from 'components';
|
||||
import { CellType } from 'common';
|
||||
|
||||
const PercentFieldCell = ({
|
||||
cell: { value: initialValue },
|
||||
@@ -38,4 +40,6 @@ const PercentFieldCell = ({
|
||||
);
|
||||
};
|
||||
|
||||
PercentFieldCell.cellType = CellType.Field;
|
||||
|
||||
export default PercentFieldCell;
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { FormGroup, Intent, Classes, Radio } from '@blueprintjs/core';
|
||||
|
||||
export function RadioTableCell({
|
||||
row: { index, original },
|
||||
column: { id, radioProps },
|
||||
cell: { value: initialValue },
|
||||
payload,
|
||||
}) {
|
||||
const [value, setValue] = React.useState(initialValue);
|
||||
|
||||
const onChange = (e) => {
|
||||
// const newValue = e.target.checked;
|
||||
// debugger;
|
||||
// setValue(newValue);
|
||||
// payload.updateData(index, id, newValue);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
const error = payload.errors?.[index]?.[id];
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
intent={error ? Intent.DANGER : null}
|
||||
className={classNames(Classes.FILL)}
|
||||
>
|
||||
<Radio
|
||||
value={value}
|
||||
label={'Warehouse #1'}
|
||||
onChange={onChange}
|
||||
checked={initialValue}
|
||||
minimal={true}
|
||||
{...radioProps}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Classes, Switch, FormGroup, Intent } from '@blueprintjs/core';
|
||||
|
||||
import { CellType } from 'common';
|
||||
import { safeInvoke } from 'utils';
|
||||
|
||||
/**
|
||||
@@ -48,4 +49,6 @@ const SwitchEditableCell = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default SwitchEditableCell;
|
||||
SwitchEditableCell.cellType = CellType.Field;
|
||||
|
||||
export default SwitchEditableCell;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Classes, TextArea, FormGroup, Intent } from '@blueprintjs/core';
|
||||
import { CellType } from 'common';
|
||||
|
||||
const TextAreaEditableCell = ({
|
||||
row: { index },
|
||||
@@ -39,4 +40,6 @@ const TextAreaEditableCell = ({
|
||||
);
|
||||
};
|
||||
|
||||
TextAreaEditableCell.cellType = CellType.Field;
|
||||
|
||||
export default TextAreaEditableCell;
|
||||
|
||||
27
src/components/DataTableCells/TextOverviewTooltipCell.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { Tooltip, Position } from '@blueprintjs/core';
|
||||
|
||||
/**
|
||||
* Text overview tooltip cell.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function TextOverviewTooltipCell({ cell: { value } }) {
|
||||
const SUBMENU_POPOVER_MODIFIERS = {
|
||||
flip: { boundariesElement: 'viewport', padding: 20 },
|
||||
offset: { offset: '0, 10' },
|
||||
preventOverflow: { boundariesElement: 'viewport', padding: 40 },
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={value}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
boundary={'viewport'}
|
||||
minimal={true}
|
||||
modifiers={SUBMENU_POPOVER_MODIFIERS}
|
||||
targetClassName={'table-tooltip-overview-target'}
|
||||
>
|
||||
{value}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -9,7 +9,8 @@ import NumericInputCell from './NumericInputCell';
|
||||
import CheckBoxFieldCell from './CheckBoxFieldCell';
|
||||
import SwitchFieldCell from './SwitchFieldCell';
|
||||
import TextAreaCell from './TextAreaCell';
|
||||
import { RadioTableCell } from './RadioFieldCell';
|
||||
import BranchesListFieldCell from './BranchesListFieldCell';
|
||||
import { TextOverviewTooltipCell } from './TextOverviewTooltipCell';
|
||||
|
||||
export {
|
||||
AccountsListFieldCell,
|
||||
@@ -24,5 +25,6 @@ export {
|
||||
CheckBoxFieldCell,
|
||||
SwitchFieldCell,
|
||||
TextAreaCell,
|
||||
RadioTableCell,
|
||||
BranchesListFieldCell,
|
||||
TextOverviewTooltipCell,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { DataTable, If } from 'components';
|
||||
import 'style/components/DataTable/DataTableEditable.scss';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { DataTable } from 'components';
|
||||
|
||||
/**
|
||||
* Editable datatable.
|
||||
@@ -11,26 +10,106 @@ export default function DatatableEditable({
|
||||
totalRow = false,
|
||||
actions,
|
||||
name,
|
||||
className,
|
||||
...tableProps
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
CLASSES.DATATABLE_EDITOR,
|
||||
{
|
||||
[`${CLASSES.DATATABLE_EDITOR}--${name}`]: name,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<DatatableEditableRoot>
|
||||
<DataTable {...tableProps} />
|
||||
|
||||
<If condition={actions}>
|
||||
<div className={classNames(CLASSES.DATATABLE_EDITOR_ACTIONS)}>
|
||||
{actions}
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
</DatatableEditableRoot>
|
||||
);
|
||||
}
|
||||
|
||||
const DatatableEditableRoot = styled.div`
|
||||
.bp3-form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.table {
|
||||
border: 1px solid #d2dce2;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
|
||||
.th,
|
||||
.td {
|
||||
border-left: 1px solid #e2e2e2;
|
||||
|
||||
&:first-of-type{
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.thead {
|
||||
.tr .th {
|
||||
padding: 9px 14px;
|
||||
background-color: #f2f3fb;
|
||||
font-size: 13px;
|
||||
color: #415060;
|
||||
border-bottom: 1px solid #d2dce2;
|
||||
|
||||
&,
|
||||
.inner-resizer {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tbody {
|
||||
.tr .td {
|
||||
border-bottom: 0;
|
||||
border-bottom: 1px solid #d8d8d8;
|
||||
min-height: 38px;
|
||||
padding: 4px 14px;
|
||||
|
||||
&.td-field-type,
|
||||
&.td-button-type{
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
.tr:last-of-type .td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.tr {
|
||||
&:hover .td,
|
||||
.bp3-input {
|
||||
background-color: transparent;
|
||||
}
|
||||
.bp3-form-group:not(.bp3-intent-danger) .bp3-input,
|
||||
.form-group--select-list .bp3-button {
|
||||
border-color: #ffffff;
|
||||
color: #222;
|
||||
border-radius: 3px;
|
||||
text-align: inherit;
|
||||
}
|
||||
.bp3-form-group:not(.bp3-intent-danger) .bp3-input {
|
||||
border-radius: 2px;
|
||||
padding-left: 14px;
|
||||
padding-right: 14px;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px #116cd0;
|
||||
}
|
||||
}
|
||||
.form-group--select-list .bp3-button {
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.form-group--select-list,
|
||||
.bp3-form-group {
|
||||
&.bp3-intent-danger {
|
||||
.bp3-button:not(.bp3-minimal),
|
||||
.bp3-input {
|
||||
border-color: #f7b6b6;
|
||||
}
|
||||
}
|
||||
}
|
||||
.td.actions {
|
||||
.bp3-button {
|
||||
color: #80858f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { If } from 'components';
|
||||
import { Skeleton } from 'components';
|
||||
import { camelCase} from 'lodash';
|
||||
|
||||
import { If, Skeleton } from 'components';
|
||||
import { useAppIntlContext } from 'components/AppIntlProvider';
|
||||
import TableContext from './TableContext';
|
||||
import { saveInvoke, ignoreEventFromSelectors } from 'utils';
|
||||
@@ -56,7 +57,8 @@ export default function TableCell({ cell, row, index }) {
|
||||
return;
|
||||
}
|
||||
saveInvoke(onCellClick, cell, event);
|
||||
};
|
||||
};
|
||||
const cellType = camelCase(cell.column.Cell.cellType) || 'text';
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -65,6 +67,9 @@ export default function TableCell({ cell, row, index }) {
|
||||
'is-text-overview': cell.column.textOverview,
|
||||
clickable: cell.column.clickable,
|
||||
'align-right': cell.column.align === 'right',
|
||||
'align-center': cell.column.align === 'center',
|
||||
[`td-${cell.column.id}`]: cell.column.id,
|
||||
[`td-${cellType}-type`]: !!cellType,
|
||||
}),
|
||||
onClick: handleCellClick,
|
||||
})}
|
||||
|
||||
@@ -80,12 +80,23 @@ function TableHeaderGroup({ headerGroup }) {
|
||||
export default function TableHeader() {
|
||||
const {
|
||||
table: { headerGroups, page },
|
||||
props: { TableHeaderSkeletonRenderer, headerLoading, progressBarLoading },
|
||||
props: {
|
||||
TableHeaderSkeletonRenderer,
|
||||
headerLoading,
|
||||
progressBarLoading,
|
||||
hideTableHeader,
|
||||
},
|
||||
} = useContext(TableContext);
|
||||
|
||||
// Can't contiunue if the thead is disabled.
|
||||
if (hideTableHeader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (headerLoading && TableHeaderSkeletonRenderer) {
|
||||
return <TableHeaderSkeletonRenderer />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollSyncPane>
|
||||
<div className="thead">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Checkbox } from '@blueprintjs/core';
|
||||
|
||||
import { CellType } from 'common';
|
||||
export default function TableIndeterminateCheckboxRow({ row }) {
|
||||
return (
|
||||
<div class="selection-checkbox">
|
||||
@@ -8,3 +8,5 @@ export default function TableIndeterminateCheckboxRow({ row }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TableIndeterminateCheckboxRow.cellType = CellType.Field;
|
||||
|
||||
34
src/components/DetailExchangeRate.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import * as R from 'ramda';
|
||||
import { DetailItem } from 'components';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||
|
||||
/**
|
||||
* Detail exchange rate item.
|
||||
* @param {*} param0
|
||||
* @param {*} param1
|
||||
* @returns
|
||||
*/
|
||||
function DetailExchangeRate({
|
||||
exchangeRate,
|
||||
toCurrency,
|
||||
// #withCurrentOrganization
|
||||
organization: { base_currency },
|
||||
}) {
|
||||
if (isEqual(base_currency, toCurrency)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DetailItem label={intl.get('exchange_rate')}>
|
||||
1 {base_currency} = {exchangeRate} {toCurrency}
|
||||
</DetailItem>
|
||||
);
|
||||
}
|
||||
|
||||
export const ExchangeRateDetailItem = R.compose(withCurrentOrganization())(
|
||||
DetailExchangeRate,
|
||||
);
|
||||
@@ -38,6 +38,13 @@ import WarehouseFormDialog from '../containers/Dialogs/WarehouseFormDialog';
|
||||
import BranchFormDialog from '../containers/Dialogs/BranchFormDialog';
|
||||
import BranchActivateDialog from '../containers/Dialogs/BranchActivateDialog';
|
||||
import WarehouseActivateDialog from '../containers/Dialogs/WarehouseActivateDialog';
|
||||
import CustomerOpeningBalanceDialog from '../containers/Dialogs/CustomerOpeningBalanceDialog';
|
||||
import VendorOpeningBalanceDialog from '../containers/Dialogs/VendorOpeningBalanceDialog';
|
||||
import ProjectFormDialog from '../containers/Projects/containers/ProjectFormDialog';
|
||||
import ProjectTaskFormDialog from '../containers/Projects/containers/ProjectTaskFormDialog';
|
||||
import ProjectTimeEntryFormDialog from '../containers/Projects/containers/ProjectTimeEntryFormDialog';
|
||||
import ProjectExpenseForm from '../containers/Projects/containers/ProjectExpenseForm';
|
||||
import EstimatedExpenseFormDialog from '../containers/Projects/containers/EstimatedExpenseFormDialog';
|
||||
|
||||
/**
|
||||
* Dialogs container.
|
||||
@@ -86,6 +93,13 @@ export default function DialogsContainer() {
|
||||
<BranchFormDialog dialogName={'branch-form'} />
|
||||
<BranchActivateDialog dialogName={'branch-activate'} />
|
||||
<WarehouseActivateDialog dialogName={'warehouse-activate'} />
|
||||
<CustomerOpeningBalanceDialog dialogName={'customer-opening-balance'} />
|
||||
<VendorOpeningBalanceDialog dialogName={'vendor-opening-balance'} />
|
||||
<ProjectFormDialog dialogName={'project-form'} />
|
||||
<ProjectTaskFormDialog dialogName={'project-task-form'} />
|
||||
<ProjectTimeEntryFormDialog dialogName={'project-time-entry-form'} />
|
||||
<ProjectExpenseForm dialogName={'project-expense-form'} />
|
||||
<EstimatedExpenseFormDialog dialogName={'estimated-expense-form'} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Classes, Icon, H4, Button } from '@blueprintjs/core';
|
||||
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import styled from 'styled-components';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
@@ -13,6 +14,7 @@ function DrawerHeaderContent(props) {
|
||||
const {
|
||||
icon,
|
||||
title = <T id={'view_paper'} />,
|
||||
subTitle,
|
||||
onClose,
|
||||
name,
|
||||
closeDrawer,
|
||||
@@ -30,7 +32,10 @@ function DrawerHeaderContent(props) {
|
||||
return (
|
||||
<div className={Classes.DRAWER_HEADER}>
|
||||
<Icon icon={icon} iconSize={Icon.SIZE_LARGE} />
|
||||
<H4>{title}</H4>
|
||||
<H4>
|
||||
{title}
|
||||
<SubTitle>{subTitle}</SubTitle>
|
||||
</H4>
|
||||
|
||||
<Button
|
||||
aria-label="Close"
|
||||
@@ -44,3 +49,24 @@ function DrawerHeaderContent(props) {
|
||||
}
|
||||
|
||||
export default compose(withDrawerActions)(DrawerHeaderContent);
|
||||
|
||||
/**
|
||||
* SubTitle Drawer header.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
function SubTitle({ children }) {
|
||||
if (children == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <SubTitleHead>{children}</SubTitleHead>;
|
||||
}
|
||||
|
||||
const SubTitleHead = styled.div`
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
padding: 2px 0px;
|
||||
margin: 2px 0px;
|
||||
`;
|
||||
|
||||
@@ -12,23 +12,20 @@ export function ExchangeRateInputGroup({
|
||||
formGroupProps,
|
||||
name,
|
||||
}) {
|
||||
const fromCountryCode = 'US';
|
||||
const toCountryCode = 'LY';
|
||||
|
||||
return (
|
||||
<FFormGroup inline={true} {...formGroupProps} name={name}>
|
||||
<ControlGroup>
|
||||
<ExchangeRatePrepend>
|
||||
<ExchangeFlagIcon countryCode={fromCountryCode} /> 1 {fromCurrency} =
|
||||
<ExchangeFlagIcon currencyCode={fromCurrency} /> 1 {fromCurrency} =
|
||||
</ExchangeRatePrepend>
|
||||
<ExchangeRateField
|
||||
allowDecimals={false}
|
||||
allowDecimals={true}
|
||||
allowNegativeValue={true}
|
||||
{...inputGroupProps}
|
||||
name={name}
|
||||
/>
|
||||
<ExchangeRateAppend>
|
||||
<ExchangeFlagIcon countryCode={toCountryCode} /> {toCurrency}
|
||||
<ExchangeFlagIcon currencyCode={toCurrency} /> {toCurrency}
|
||||
</ExchangeRateAppend>
|
||||
</ControlGroup>
|
||||
</FFormGroup>
|
||||
@@ -36,7 +33,7 @@ export function ExchangeRateInputGroup({
|
||||
}
|
||||
|
||||
const ExchangeRateField = styled(FMoneyInputGroup)`
|
||||
max-width: 88px;
|
||||
max-width: 75px;
|
||||
`;
|
||||
|
||||
const ExchangeRateSideIcon = styled.div`
|
||||
|
||||
94
src/components/ExchangeRate/ExchangeRateMutedField.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import intl from 'react-intl-universal';
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
PopoverInteractionKind,
|
||||
FormGroup,
|
||||
Position,
|
||||
Classes,
|
||||
} from '@blueprintjs/core';
|
||||
import { ExchangeRateInputGroup, Icon } from 'components';
|
||||
|
||||
export function ExchangeRateMutedField({
|
||||
name,
|
||||
toCurrency,
|
||||
fromCurrency,
|
||||
date,
|
||||
exchangeRate,
|
||||
exchangeRateFieldProps,
|
||||
popoverProps,
|
||||
}) {
|
||||
const content = (
|
||||
<ExchangeRateFormGroupContent>
|
||||
<ExchangeRateInputGroup
|
||||
name={name}
|
||||
fromCurrency={fromCurrency}
|
||||
toCurrency={toCurrency}
|
||||
{...exchangeRateFieldProps}
|
||||
/>
|
||||
</ExchangeRateFormGroupContent>
|
||||
);
|
||||
|
||||
return (
|
||||
<ExchangeRateFormGroup label={`As on ${date},`}>
|
||||
<Popover
|
||||
content={content}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.RIGHT_TOP}
|
||||
modifiers={{
|
||||
offset: { offset: '0, 4' },
|
||||
}}
|
||||
{...popoverProps}
|
||||
minimal={true}
|
||||
usePortal={false}
|
||||
target={<div />}
|
||||
>
|
||||
<ExchangeRateButton>
|
||||
1 {fromCurrency} = {exchangeRate} {toCurrency}
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
rightIcon={<Icon icon="pen-18" iconSize={14} />}
|
||||
small={true}
|
||||
/>
|
||||
</ExchangeRateButton>
|
||||
</Popover>
|
||||
</ExchangeRateFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const ExchangeRateFormGroup = styled(FormGroup)`
|
||||
&.bp3-form-group {
|
||||
label.bp3-label {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
line-height: 1;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ExchangeRateButton = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: #0d244a;
|
||||
position: relative;
|
||||
padding-right: 28px;
|
||||
|
||||
.bp3-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const ExchangeRateFormGroupContent = styled.div`
|
||||
padding: 5px 0;
|
||||
|
||||
.bp3-form-group {
|
||||
padding: 2px;
|
||||
margin: 2px 4px !important;
|
||||
}
|
||||
`;
|
||||
@@ -1 +1,2 @@
|
||||
export * from './ExchangeRateInput';
|
||||
export * from './ExchangeRateInput';
|
||||
export * from './ExchangeRateMutedField'
|
||||
@@ -2,7 +2,8 @@ import React, { useMemo, useCallback } from 'react';
|
||||
import moment from 'moment';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { If, FormattedMessage as T } from 'components';
|
||||
import If from '../Utils/If';
|
||||
import { FormattedMessage as T } from '../FormattedMessage';
|
||||
import {
|
||||
FinancialSheetRoot,
|
||||
FinancialSheetFooterCurrentTime,
|
||||
|
||||
@@ -2,7 +2,8 @@ import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Align } from 'common';
|
||||
import { SkeletonText, DataTable } from 'components';
|
||||
import { SkeletonText } from 'components';
|
||||
import DataTable from '../../components/DataTable'
|
||||
|
||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||
|
||||
9
src/components/FinancialSheet/ReportDataTable.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import DataTable from '../DataTable';
|
||||
|
||||
export const ReportDataTable = styled(DataTable)`
|
||||
.table .tbody .tr.no-results:last-of-type .td {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
`;
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './FinancialSheet';
|
||||
export * from './FinancialSheetSkeleton';
|
||||
export * from './FinancialSheetSkeleton';
|
||||
export * from './ReportDataTable';
|
||||
16
src/components/FlexGrid/Flex.style.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import styled from 'styled-components';
|
||||
import { FlexProps } from './interfaces';
|
||||
|
||||
export const FlexStyled = styled.div<FlexProps>`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
align-items: ${({ align }) => align || 'center'};
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
max-width: ${({ col, gap = 1 }) =>
|
||||
col && col < 12 ? `${(100 * col) / 12 - gap}%` : '100%'};
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
26
src/components/FlexGrid/Flex.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as React from 'react';
|
||||
import { FlexProps } from './interfaces';
|
||||
import { FlexItem } from './FlexItem';
|
||||
import { FlexStyled } from './Flex.style';
|
||||
|
||||
export function Flex({
|
||||
children,
|
||||
col = 12,
|
||||
gap,
|
||||
align,
|
||||
className,
|
||||
style,
|
||||
}: FlexProps) {
|
||||
return (
|
||||
<FlexStyled
|
||||
col={col}
|
||||
gap={gap}
|
||||
align={align}
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
<FlexItem col={col} gap={gap} />
|
||||
</FlexStyled>
|
||||
);
|
||||
}
|
||||
19
src/components/FlexGrid/FlexItem.style.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import styled from 'styled-components';
|
||||
import { ItemProps } from './interfaces';
|
||||
|
||||
export const FlexItem = styled.div<ItemProps>`
|
||||
width: 100%;
|
||||
max-width: ${({ col, gap = 1 }) =>
|
||||
col && col < 12 ? `${(100 * col) / 12 - gap}%` : '100%'};
|
||||
${({ marginBottom }) =>
|
||||
marginBottom &&
|
||||
`
|
||||
margin-bottom: ${marginBottom}px;
|
||||
`}
|
||||
${({ stretch }) =>
|
||||
stretch &&
|
||||
`
|
||||
display: flex;
|
||||
align-self: stretch;
|
||||
`}
|
||||
`;
|
||||
1
src/components/FlexGrid/FlexItem.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from './FlexItem.style';
|
||||
4
src/components/FlexGrid/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './Flex.style';
|
||||
export * from './Flex';
|
||||
export * from './FlexItem.style';
|
||||
export * from './interfaces';
|
||||
22
src/components/FlexGrid/interfaces.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { HTMLAttributes, Component, StyleHTMLAttributes } from 'react';
|
||||
|
||||
export type Range = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||
|
||||
export interface ItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||
gap?: number;
|
||||
col: Range;
|
||||
marginBottom?: number;
|
||||
stretch?: boolean;
|
||||
as?: string | Component;
|
||||
className?: string;
|
||||
style?: StyleHTMLAttributes<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export interface FlexProps extends HTMLAttributes<HTMLDivElement> {
|
||||
gap?: number;
|
||||
align?: 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch';
|
||||
col?: Range;
|
||||
className?: string;
|
||||
style?: StyleHTMLAttributes<HTMLDivElement>;
|
||||
as?: string | Component;
|
||||
}
|
||||
44
src/components/FormTopbar.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Navbar } from '@blueprintjs/core';
|
||||
|
||||
/**
|
||||
* Form the topbar.
|
||||
* @param {JSX.Element} children
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function FormTopbar({ className, children }) {
|
||||
return <FormTopBarRoot className={className}>{children}</FormTopBarRoot>;
|
||||
}
|
||||
|
||||
const FormTopBarRoot = styled(Navbar)`
|
||||
box-shadow: 0 0 0;
|
||||
border-bottom: 1px solid #c7d5db;
|
||||
height: 35px;
|
||||
padding: 0 20px;
|
||||
|
||||
.bp3-navbar-group {
|
||||
height: 35px;
|
||||
}
|
||||
.bp3-navbar-divider {
|
||||
border-left-color: #d2dce2;
|
||||
}
|
||||
.bp3-skeleton {
|
||||
max-height: 10px;
|
||||
}
|
||||
.bp3-button {
|
||||
&:hover {
|
||||
background: rgba(167, 182, 194, 0.12);
|
||||
color: #32304a;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DetailsBarSkeletonBase = styled.div`
|
||||
letter-spacing: 10px;
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
font-size: 8px;
|
||||
width: 140px;
|
||||
height: 10px;
|
||||
`;
|
||||
@@ -5,8 +5,11 @@ import {
|
||||
Checkbox,
|
||||
RadioGroup,
|
||||
Switch,
|
||||
EditableText,
|
||||
TextArea,
|
||||
} from '@blueprintjs-formik/core';
|
||||
import { Select, MultiSelect } from '@blueprintjs-formik/select';
|
||||
import { DateInput } from '@blueprintjs-formik/datetime';
|
||||
|
||||
export {
|
||||
FormGroup as FFormGroup,
|
||||
@@ -17,4 +20,7 @@ export {
|
||||
Switch as FSwitch,
|
||||
Select as FSelect,
|
||||
MultiSelect as FMultiSelect,
|
||||
EditableText as FEditableText,
|
||||
TextArea as FTextArea,
|
||||
DateInput as FDateInput,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
|
||||
import { useFormikContext } from 'formik';
|
||||
import { useDeepCompareEffect } from 'hooks/utils';
|
||||
|
||||
export function FormikObserver({ onChange, values }) {
|
||||
export function FormikObserver({ onChange }) {
|
||||
const { values } = useFormikContext();
|
||||
|
||||
useDeepCompareEffect(() => {
|
||||
onChange(values);
|
||||
}, [values]);
|
||||
|
||||
@@ -104,7 +104,7 @@ import {
|
||||
// textClassName?: string;
|
||||
// }
|
||||
|
||||
export default class MenuItem extends AbstractPureComponent2 {
|
||||
export class MenuItem extends AbstractPureComponent2 {
|
||||
static get defaultProps() {
|
||||
return {
|
||||
disabled: false,
|
||||
|
||||
12
src/components/Paper/Paper.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export function Paper({ children, className }) {
|
||||
return <PaperRoot className={className}>{children}</PaperRoot>;
|
||||
}
|
||||
|
||||
const PaperRoot = styled.div`
|
||||
border: 1px solid #d2dce2;
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
`;
|
||||
1
src/components/Paper/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from './Paper';
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import SidebarContainer from 'components/Sidebar/SidebarContainer';
|
||||
import SidebarHead from 'components/Sidebar/SidebarHead';
|
||||
import SidebarMenu from 'components/Sidebar/SidebarMenu';
|
||||
import { useGetSidebarMenu } from './utils';
|
||||
|
||||
import 'style/containers/Dashboard/Sidebar.scss';
|
||||
|
||||
export default function Sidebar({ dashboardContentRef }) {
|
||||
const menu = useGetSidebarMenu();
|
||||
|
||||
return (
|
||||
<SidebarContainer>
|
||||
<SidebarHead />
|
||||
|
||||
<div className="sidebar__menu">
|
||||
<SidebarMenu menu={menu} />
|
||||
</div>
|
||||
|
||||
<SidebarFooterVersion />
|
||||
</SidebarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar footer version.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
function SidebarFooterVersion() {
|
||||
const { REACT_APP_VERSION: VERSION } = process.env;
|
||||
|
||||
if (!VERSION) {
|
||||
return null;
|
||||
}
|
||||
return <div class="sidebar__version">v{VERSION}</div>;
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Menu, MenuDivider } from '@blueprintjs/core';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
|
||||
import { Choose } from 'components';
|
||||
import Icon from 'components/Icon';
|
||||
import MenuItem from 'components/MenuItem';
|
||||
import { MenuItemLabel } from 'components';
|
||||
import classNames from 'classnames';
|
||||
import SidebarOverlay from 'components/SidebarOverlay';
|
||||
import { compose } from 'redux';
|
||||
import withSubscriptions from '../../containers/Subscriptions/withSubscriptions';
|
||||
|
||||
const DEFAULT_ITEM = {
|
||||
text: '',
|
||||
href: '',
|
||||
};
|
||||
|
||||
function matchPath(pathname, path, matchExact) {
|
||||
return matchExact ? pathname === path : pathname.indexOf(path) !== -1;
|
||||
}
|
||||
|
||||
function SidebarMenuItemSpace({ space }) {
|
||||
return <div class="bp3-menu-spacer" style={{ height: `${space}px` }} />;
|
||||
}
|
||||
|
||||
function SidebarMenu({ menu, isSubscriptionActive }) {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const [currentItem, setCurrentItem] = React.useState(null);
|
||||
|
||||
const menuItemsMapper = (list) => {
|
||||
return list.map((item, index) => {
|
||||
const hasChildren = Array.isArray(item.children);
|
||||
|
||||
const isActive =
|
||||
(item.children
|
||||
? item.children.some((c) =>
|
||||
matchPath(location.pathname, c.href, item.matchExact),
|
||||
)
|
||||
: item.href &&
|
||||
matchPath(location.pathname, item.href, item.matchExact)) ||
|
||||
currentItem === item;
|
||||
|
||||
const handleItemClick = () => {
|
||||
if (item.href) {
|
||||
history.push(item.href);
|
||||
}
|
||||
if (item.children && item.children.length > 0) {
|
||||
setIsOpen(true);
|
||||
setCurrentItem(item);
|
||||
} else {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Choose>
|
||||
<Choose.When condition={item.spacer}>
|
||||
<SidebarMenuItemSpace space={item.spacer} />
|
||||
</Choose.When>
|
||||
|
||||
<Choose.When condition={item.divider}>
|
||||
<MenuDivider key={index} title={item.title} />
|
||||
</Choose.When>
|
||||
|
||||
<Choose.When condition={item.label}>
|
||||
<MenuItemLabel key={index} text={item.text} />
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
<MenuItem
|
||||
key={index}
|
||||
active={isActive}
|
||||
icon={<Icon icon={item.icon} iconSize={item.iconSize} />}
|
||||
text={item.text}
|
||||
disabled={item.disabled}
|
||||
dropdownType={item.dropdownType || 'collapse'}
|
||||
caretIconSize={16}
|
||||
onClick={handleItemClick}
|
||||
callapseActive={!!isActive}
|
||||
itemClassName={classNames({
|
||||
'is-active': isActive,
|
||||
'has-icon': !hasChildren && item.icon,
|
||||
})}
|
||||
hasSubmenu={hasChildren}
|
||||
/>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const filterItems = menu.filter(
|
||||
(item) => isSubscriptionActive || item.enableBilling,
|
||||
);
|
||||
const items = menuItemsMapper(filterItems);
|
||||
|
||||
const handleSidebarOverlayClose = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Menu className="sidebar-menu">{items}</Menu>{' '}
|
||||
<SidebarOverlay
|
||||
isOpen={isOpen}
|
||||
label={currentItem?.text || ''}
|
||||
items={currentItem?.children || []}
|
||||
onClose={handleSidebarOverlayClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withSubscriptions(
|
||||
({ isSubscriptionActive }) => ({ isSubscriptionActive }),
|
||||
'main',
|
||||
),
|
||||
)(SidebarMenu);
|
||||
@@ -1,48 +0,0 @@
|
||||
import sidebarMenuList from 'config/sidebarMenu';
|
||||
import { isArray, isEmpty } from 'lodash';
|
||||
import { useAbilityContext } from 'hooks/utils';
|
||||
|
||||
export function useGetSidebarMenu() {
|
||||
const ability = useAbilityContext();
|
||||
|
||||
return sidebarMenuList
|
||||
.map((item) => {
|
||||
const children = isArray(item.children)
|
||||
? item.children.filter((childItem) => {
|
||||
return isArray(childItem.permission)
|
||||
? childItem.permission.some((perm) =>
|
||||
ability.can(perm.ability, perm.subject),
|
||||
)
|
||||
: childItem?.permission?.ability && childItem?.permission?.subject
|
||||
? ability.can(
|
||||
childItem.permission.ability,
|
||||
childItem.permission.subject,
|
||||
)
|
||||
: true;
|
||||
})
|
||||
: [];
|
||||
|
||||
return {
|
||||
...item,
|
||||
...(isArray(item.children)
|
||||
? {
|
||||
children,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
})
|
||||
.filter((item) => {
|
||||
return isArray(item.permission)
|
||||
? item.permission.some((per) =>
|
||||
ability.can(per.ability, per.subject),
|
||||
)
|
||||
: item?.permission?.ability && item?.permission?.subject
|
||||
? ability.can(item.permission.ability, item.permission.subject)
|
||||
: true;
|
||||
})
|
||||
.filter((item) =>
|
||||
isEmpty(item.children) && !item.href && !item.label && !item.divider
|
||||
? false
|
||||
: true,
|
||||
);
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Overlay } from '@blueprintjs/core';
|
||||
import { Link } from 'react-router-dom';
|
||||
import SidebarOverlayContainer from './SidebarOverlayContainer';
|
||||
interface ISidebarOverlayItem {
|
||||
text: string;
|
||||
href: string;
|
||||
divider?: boolean;
|
||||
label?: boolean;
|
||||
}
|
||||
|
||||
interface ISidebarOverlayProps {
|
||||
isOpen: boolean;
|
||||
items: ISidebarOverlayItem[];
|
||||
label: string;
|
||||
onClose: Function;
|
||||
}
|
||||
|
||||
interface ISidebarOverlayItemProps {
|
||||
text: string;
|
||||
href: string;
|
||||
onLinkClick: Function;
|
||||
}
|
||||
|
||||
interface ISidebarOverlayItemDivider {
|
||||
divider: boolean;
|
||||
}
|
||||
/**
|
||||
* Sidebar overlay item.
|
||||
*/
|
||||
function SidebarOverlayItem({
|
||||
text,
|
||||
href,
|
||||
onLinkClick,
|
||||
}: ISidebarOverlayItemProps) {
|
||||
const handleLinkClick = () => {
|
||||
onLinkClick && onLinkClick();
|
||||
};
|
||||
return (
|
||||
<div className="sidebar-overlay__item">
|
||||
<Link onClick={handleLinkClick} to={href}>
|
||||
{text}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface ISidebarOverlayItemLabel {
|
||||
text: string;
|
||||
}
|
||||
|
||||
function SidebarOverlayLabel({ text }: ISidebarOverlayItemLabel) {
|
||||
return <div className="sidebar-overlay__label">{text}</div>;
|
||||
}
|
||||
|
||||
function SidebarOverlayDivider() {
|
||||
return <div className={'sidebar-overlay__divider'}></div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar overlay component.
|
||||
*/
|
||||
export default function SidebarOverlay({
|
||||
label,
|
||||
isOpen: controllerdIsOpen,
|
||||
onClose,
|
||||
items,
|
||||
}: ISidebarOverlayProps) {
|
||||
const [isEverOpened, setEverOpened] = React.useState(false);
|
||||
const [isOpen, setIsOpen] = React.useState(controllerdIsOpen);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
typeof controllerdIsOpen !== 'undefined' &&
|
||||
isOpen !== controllerdIsOpen
|
||||
) {
|
||||
setIsOpen(controllerdIsOpen);
|
||||
}
|
||||
}, [controllerdIsOpen, setIsOpen, isOpen]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isOpen && !isEverOpened) {
|
||||
setEverOpened(true);
|
||||
}
|
||||
}, [isEverOpened, isOpen]);
|
||||
|
||||
if (!isEverOpened) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Handle overlay close event.
|
||||
const handleOverlayClose = () => {
|
||||
setIsOpen(false);
|
||||
onClose && onClose();
|
||||
};
|
||||
// Handle overlay open event.
|
||||
const handleOverlayOpen = () => {
|
||||
setIsOpen(true);
|
||||
};
|
||||
// Handle sidebar item link click.
|
||||
const handleItemClick = () => {
|
||||
setIsOpen(false);
|
||||
onClose && onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Overlay
|
||||
isOpen={isOpen}
|
||||
portalContainer={
|
||||
(document.querySelector('.Pane.vertical.Pane2') as HTMLElement) ||
|
||||
document.body
|
||||
}
|
||||
onClose={handleOverlayClose}
|
||||
onOpening={handleOverlayOpen}
|
||||
transitionDuration={100}
|
||||
backdropClassName={'sidebar-overlay-backdrop'}
|
||||
>
|
||||
<div className="sidebar-overlay sidebar-overlay-transition">
|
||||
<SidebarOverlayContainer>
|
||||
<div className="sidebar-overlay__menu">
|
||||
{label && (
|
||||
<>
|
||||
<SidebarOverlayLabel text={label} />
|
||||
<SidebarOverlayDivider />
|
||||
</>
|
||||
)}
|
||||
|
||||
{items.map((item) =>
|
||||
item.divider ? (
|
||||
<SidebarOverlayDivider />
|
||||
) : item.label ? (
|
||||
<SidebarOverlayLabel text={item.text} />
|
||||
) : (
|
||||
<SidebarOverlayItem
|
||||
onLinkClick={handleItemClick}
|
||||
text={item.text}
|
||||
href={item.href}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</SidebarOverlayContainer>
|
||||
</div>
|
||||
</Overlay>
|
||||
);
|
||||
}
|
||||
@@ -9,3 +9,13 @@ export const CurrencyTag = styled.span`
|
||||
line-height: 1;
|
||||
margin-left: 4px;
|
||||
`;
|
||||
|
||||
export const BaseCurrencyRoot = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 10px;
|
||||
margin-left: 4px;
|
||||
> span {
|
||||
background: #5c7080;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
export const FlagIcon = ({ countryCode, className }) => {
|
||||
const source = `/icons/flags/${countryCode}.svg`;
|
||||
export const FlagIcon = ({ currencyCode, className }) => {
|
||||
const source = `/icons/flags/${currencyCode}.svg`;
|
||||
|
||||
return <img alt="flag" src={source} className={className} />;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const TotalLineBorderStyle = {
|
||||
None: 'None',
|
||||
SingleDark: 'SingleDark',
|
||||
DoubleDark: 'DoubleDark',
|
||||
};
|
||||
@@ -80,6 +81,11 @@ export const TotalLineRoot = styled.div`
|
||||
`
|
||||
border-bottom: 1px double #000;
|
||||
`}
|
||||
${(props) =>
|
||||
props.borderStyle === TotalLineBorderStyle.None &&
|
||||
`
|
||||
border-bottom-color: transparent;
|
||||
`}
|
||||
${(props) =>
|
||||
props.textStyle === TotalLineTextStyle.Bold &&
|
||||
`
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { ButtonLink } from 'components';
|
||||
import { ButtonLink } from '../Button';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
function VendorDrawerLinkComponent({
|
||||
// #ownProps
|
||||
children,
|
||||
vendorId,
|
||||
className,
|
||||
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
}) {
|
||||
// Handle view customer drawer.
|
||||
const handleVendorDrawer = () => {
|
||||
const handleVendorDrawer = (event) => {
|
||||
openDrawer('vendor-details-drawer', { vendorId });
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
return <ButtonLink onClick={handleVendorDrawer}>{children}</ButtonLink>;
|
||||
return <ButtonLink className={className} onClick={handleVendorDrawer}>{children}</ButtonLink>;
|
||||
}
|
||||
|
||||
export const VendorDrawerLink = R.compose(withDrawerActions)(VendorDrawerLinkComponent);
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled, { createGlobalStyle } from 'styled-components';
|
||||
import { PopoverInteractionKind, Position } from '@blueprintjs/core';
|
||||
import { Popover2 } from '@blueprintjs/popover2';
|
||||
|
||||
import { DataTable } from 'components';
|
||||
import { RadioTableCell } from 'components/DataTableCells';
|
||||
import { compose, updateTableCell } from 'utils';
|
||||
|
||||
import { TableStyle } from 'common';
|
||||
|
||||
const Classes = {
|
||||
WAREHOUSE_ITEM_ENTRY_POPOVER: 'warehouse-item-entry-popover',
|
||||
};
|
||||
/**
|
||||
* Retrieve warehouse entries columns.
|
||||
*/
|
||||
export function useWarehouseEntriesColumns() {
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Warehouse Name',
|
||||
accessor: 'warehouse_name',
|
||||
Cell: RadioTableCell,
|
||||
width: 120,
|
||||
disableSortBy: true,
|
||||
className: 'name',
|
||||
},
|
||||
{
|
||||
id: 'quantity',
|
||||
Header: 'Quantity on Hand',
|
||||
accessor: 'quantity',
|
||||
disableSortBy: true,
|
||||
align: 'right',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
id: 'availiable_for_sale',
|
||||
Header: 'Availiable for Sale',
|
||||
accessor: 'availiable_for_sale',
|
||||
disableSortBy: true,
|
||||
align: 'right',
|
||||
width: 60,
|
||||
className: 'availiable_for_sale',
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
export function IntegrateWarehouseTable({ children }) {
|
||||
return (
|
||||
<>
|
||||
<GlobalPopoverStyled />
|
||||
<Popover2
|
||||
// minimal={true}
|
||||
content={<IntegrateWarehousesTable />}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_RIGHT}
|
||||
popoverClassName={Classes.WAREHOUSE_ITEM_ENTRY_POPOVER}
|
||||
autoFocus={false}
|
||||
enforceFocus={false}
|
||||
shouldReturnFocusOnClose={false}
|
||||
>
|
||||
{children}
|
||||
</Popover2>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const PopoverLink = styled.button`
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
margin-top: 18px;
|
||||
padding-right: 0px;
|
||||
color: #0052cc;
|
||||
&:hover,
|
||||
&:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
export function IntegrateWarehousesTable({
|
||||
// #ownProps
|
||||
onUpdateData,
|
||||
entries,
|
||||
errors,
|
||||
}) {
|
||||
// warehouses entries columns.
|
||||
const columns = useWarehouseEntriesColumns();
|
||||
|
||||
// Handle update data.
|
||||
const handleUpdateData = React.useCallback(
|
||||
(rowIndex, columnId, value) => {
|
||||
const newRows = compose(updateTableCell(rowIndex, columnId, value))(
|
||||
entries,
|
||||
);
|
||||
onUpdateData(newRows);
|
||||
},
|
||||
[onUpdateData, entries],
|
||||
);
|
||||
|
||||
const data = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
warehouse_name: true,
|
||||
quantity: '9,444',
|
||||
availiable_for_sale: 0,
|
||||
},
|
||||
{
|
||||
warehouse_name: false,
|
||||
quantity: '19,444',
|
||||
availiable_for_sale: 0,
|
||||
},
|
||||
{
|
||||
warehouse_name: false,
|
||||
quantity: '19,444',
|
||||
availiable_for_sale: 0,
|
||||
},
|
||||
|
||||
{
|
||||
warehouse_name: false,
|
||||
quantity: '19,444',
|
||||
availiable_for_sale: 0,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<WarehousesRoot>
|
||||
<PopoverHeader>Warehouses</PopoverHeader>
|
||||
|
||||
<WarehouseDataTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
styleName={TableStyle.Constrant}
|
||||
payload={{
|
||||
errors: errors || [],
|
||||
updateData: handleUpdateData,
|
||||
}}
|
||||
/>
|
||||
|
||||
<WarehousesDesc>
|
||||
<WarehousesDescItem>
|
||||
Stock on Hand : This is calculated based on Bills and Invoices.
|
||||
</WarehousesDescItem>
|
||||
<WarehousesDescItem>
|
||||
Available for Sale : Stock on Hand - Committed Stock
|
||||
</WarehousesDescItem>
|
||||
</WarehousesDesc>
|
||||
</WarehousesRoot>
|
||||
);
|
||||
}
|
||||
|
||||
const WarehouseDataTable = styled(DataTable)`
|
||||
.table {
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #888;
|
||||
|
||||
.thead .tr:first-of-type .th,
|
||||
.thead .tr:first-of-type .th {
|
||||
border-top-color: #888;
|
||||
}
|
||||
.thead .tr .th {
|
||||
border-bottom-color: #888;
|
||||
font-size: 12px;
|
||||
}
|
||||
.tbody .tr .td {
|
||||
padding: 0.6rem 0.6rem;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
border-left: 1px solid #e6e6e6;
|
||||
}
|
||||
}
|
||||
|
||||
.tbody {
|
||||
max-height: 145px;
|
||||
|
||||
.bp3-form-group .bp3-control,
|
||||
.bp3-form-group {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tr .td.availiable_for_sale {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const WarehousesRoot = styled.div`
|
||||
width: 500px;
|
||||
`;
|
||||
|
||||
const WarehousesDescItem = styled.div`
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
`;
|
||||
const WarehousesDesc = styled.div`
|
||||
font-size: 10px;
|
||||
padding: 12px 15px;
|
||||
`;
|
||||
|
||||
const PopoverHeader = styled.div`
|
||||
color: #222;
|
||||
line-height: 1;
|
||||
padding: 10px 15px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
const GlobalPopoverStyled = createGlobalStyle`
|
||||
.${Classes.WAREHOUSE_ITEM_ENTRY_POPOVER}{
|
||||
&.bp3-popover2,
|
||||
.bp3-popover2-content{
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -40,8 +40,8 @@ const warehouseItemRenderer = (
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
icon={isSelected ? 'tick' : 'blank'}
|
||||
text={warehouse.name.toString()}
|
||||
label={warehouse.code.toString()}
|
||||
text={warehouse.name}
|
||||
label={warehouse.code}
|
||||
key={warehouse.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
@@ -52,7 +52,7 @@ const warehouseSelectProps = {
|
||||
itemPredicate: warehouseItemPredicate,
|
||||
itemRenderer: warehouseItemRenderer,
|
||||
valueAccessor: (item) => item.id,
|
||||
labelAccessor: (item) => item.label,
|
||||
labelAccessor: (item) => item.code,
|
||||
tagRenderer: (item) => item.name,
|
||||
};
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ const warehouseItemRenderer = (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
label={warehouse.name.toString()}
|
||||
label={warehouse.code}
|
||||
key={warehouse.id}
|
||||
onClick={handleClick}
|
||||
text={text}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './WarehouseSelect';
|
||||
export * from './WarehouseMultiSelect';
|
||||
export * from './WarehouseItemEntrySelectPopover'
|
||||
@@ -49,13 +49,12 @@ import DrawerHeaderContent from './Drawer/DrawerHeaderContent';
|
||||
import Postbox from './Postbox';
|
||||
import AccountsSuggestField from './AccountsSuggestField';
|
||||
import MaterialProgressBar from './MaterialProgressBar';
|
||||
import { MoneyFieldCell } from './DataTableCells';
|
||||
import AvaterCell from './AvaterCell';
|
||||
|
||||
import { ItemsMultiSelect } from './Items';
|
||||
import MoreMenuItems from './MoreMenutItems';
|
||||
import CustomSelectList from './CustomSelectList';
|
||||
import BaseCurrency from './BaseCurrency';
|
||||
import { ExchangeRateDetailItem } from './DetailExchangeRate';
|
||||
|
||||
export * from './Dialog';
|
||||
export * from './Menu';
|
||||
@@ -102,6 +101,12 @@ export * from './ExchangeRate';
|
||||
export * from './Branches';
|
||||
export * from './Warehouses';
|
||||
export * from './Currencies';
|
||||
export * from './FormTopbar';
|
||||
export * from './Paper';
|
||||
export * from './Accounts';
|
||||
export * from './DataTableCells';
|
||||
export * from './FlexGrid';
|
||||
export * from './MenuItem';
|
||||
|
||||
const Hint = FieldHint;
|
||||
|
||||
@@ -164,10 +169,9 @@ export {
|
||||
Postbox,
|
||||
AccountsSuggestField,
|
||||
MaterialProgressBar,
|
||||
MoneyFieldCell,
|
||||
ItemsMultiSelect,
|
||||
AvaterCell,
|
||||
BaseCurrency,
|
||||
MoreMenuItems,
|
||||
CustomSelectList,
|
||||
ExchangeRateDetailItem,
|
||||
};
|
||||
|
||||
@@ -70,6 +70,20 @@ export const financialReportMenus = [
|
||||
subject: AbilitySubject.Report,
|
||||
ability: ReportsAction.READ_AP_AGING_SUMMARY,
|
||||
},
|
||||
{
|
||||
title: <T id={'report.balance_sheet_comparison.title'} />,
|
||||
desc: <T id={'report.balance_sheet_comparison.desc'} />,
|
||||
link: 'financial-reports/balance-sheet?previousYear=true&previousYearAmountChange=true&previousYearPercentageChange=true',
|
||||
subject: AbilitySubject.Report,
|
||||
ability: ReportsAction.READ_BALANCE_SHEET,
|
||||
},
|
||||
{
|
||||
title: <T id={'report.profit_loss_sheet_comparison.title'} />,
|
||||
desc: <T id={'report.profit_loss_sheet_comparison.desc'} />,
|
||||
link: '/financial-reports/profit-loss-sheet?previousYear=true&previousYearAmountChange=true&previousYearPercentageChange=true',
|
||||
subject: AbilitySubject.Report,
|
||||
ability: ReportsAction.READ_PROFIT_LOSS,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -13,13 +13,13 @@ const Schema = Yup.object().shape({
|
||||
.min(1)
|
||||
.max(DATATYPES_LENGTH.STRING)
|
||||
.label(intl.get('journal_type')),
|
||||
date: Yup.date()
|
||||
.required()
|
||||
.label(intl.get('date')),
|
||||
date: Yup.date().required().label(intl.get('date')),
|
||||
currency_code: Yup.string().max(3),
|
||||
publish: Yup.boolean(),
|
||||
branch_id: Yup.string(),
|
||||
reference: Yup.string().nullable().min(1).max(DATATYPES_LENGTH.STRING),
|
||||
description: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
|
||||
exchange_rate: Yup.number(),
|
||||
entries: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
credit: Yup.number().nullable(),
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||
* Make journal entries field.
|
||||
*/
|
||||
export default function MakeJournalEntriesField() {
|
||||
const { accounts, contacts } = useMakeJournalFormContext();
|
||||
const { accounts, contacts ,branches } = useMakeJournalFormContext();
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||
@@ -18,6 +18,7 @@ export default function MakeJournalEntriesField() {
|
||||
name={'entries'}
|
||||
contacts={contacts}
|
||||
accounts={accounts}
|
||||
branches={branches}
|
||||
shouldUpdate={entriesFieldShouldUpdate}
|
||||
>
|
||||
{({
|
||||
|
||||
@@ -17,6 +17,7 @@ import MakeJournalFormFloatingActions from './MakeJournalFormFloatingActions';
|
||||
import MakeJournalEntriesField from './MakeJournalEntriesField';
|
||||
import MakeJournalFormFooter from './MakeJournalFormFooter';
|
||||
import MakeJournalFormDialogs from './MakeJournalFormDialogs';
|
||||
import MakeJournalFormTopBar from './MakeJournalFormTopBar';
|
||||
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||
@@ -58,6 +59,7 @@ function MakeJournalEntriesForm({
|
||||
journalNumberPrefix,
|
||||
journalNextNumber,
|
||||
);
|
||||
|
||||
// Form initial values.
|
||||
const initialValues = useMemo(
|
||||
() => ({
|
||||
@@ -68,12 +70,12 @@ function MakeJournalEntriesForm({
|
||||
: {
|
||||
...defaultManualJournal,
|
||||
...(journalAutoIncrement && {
|
||||
journal_number: defaultTo(journalNumber, ''),
|
||||
journal_number: journalNumber,
|
||||
}),
|
||||
currency_code: base_currency,
|
||||
}),
|
||||
}),
|
||||
[manualJournal, base_currency, journalNumber],
|
||||
[manualJournal, base_currency, journalNumber, journalAutoIncrement],
|
||||
);
|
||||
|
||||
// Handle the form submiting.
|
||||
@@ -107,7 +109,7 @@ function MakeJournalEntriesForm({
|
||||
return;
|
||||
}
|
||||
const form = {
|
||||
...omit(values, ['journal_number', 'journal_number_manually']),
|
||||
...omit(values, ['journal_number_manually']),
|
||||
...(values.journal_number_manually && {
|
||||
journal_number: values.journal_number,
|
||||
}),
|
||||
@@ -169,6 +171,7 @@ function MakeJournalEntriesForm({
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Form>
|
||||
<MakeJournalFormTopBar />
|
||||
<MakeJournalEntriesHeader />
|
||||
<MakeJournalEntriesField />
|
||||
<MakeJournalFormFooter />
|
||||
@@ -185,7 +188,7 @@ function MakeJournalEntriesForm({
|
||||
export default compose(
|
||||
withMediaActions,
|
||||
withSettings(({ manualJournalsSettings }) => ({
|
||||
journalNextNumber: parseInt(manualJournalsSettings?.nextNumber, 10),
|
||||
journalNextNumber: manualJournalsSettings?.nextNumber,
|
||||
journalNumberPrefix: manualJournalsSettings?.numberPrefix,
|
||||
journalAutoIncrement: manualJournalsSettings?.autoIncrement,
|
||||
})),
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from 'components';
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||
import { JournalExchangeRateInputField } from './components';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import {
|
||||
currenciesFieldShouldUpdate,
|
||||
@@ -52,14 +53,14 @@ function MakeJournalEntriesHeader({
|
||||
|
||||
// Handle journal number change.
|
||||
const handleJournalNumberChange = () => {
|
||||
openDialog('journal-number-form', {});
|
||||
openDialog('journal-number-form');
|
||||
};
|
||||
|
||||
// Handle journal number blur.
|
||||
const handleJournalNoBlur = (form, field) => (event) => {
|
||||
const newValue = event.target.value;
|
||||
|
||||
if (field.value !== newValue) {
|
||||
if (field.value !== newValue && journalAutoIncrement) {
|
||||
openDialog('journal-number-form', {
|
||||
initialFormValues: {
|
||||
manualTransactionNo: newValue,
|
||||
@@ -201,13 +202,19 @@ function MakeJournalEntriesHeader({
|
||||
selectedCurrencyCode={value}
|
||||
onCurrencySelected={(currencyItem) => {
|
||||
form.setFieldValue('currency_code', currencyItem.currency_code);
|
||||
form.setFieldValue('exchange_rate', '');
|
||||
}}
|
||||
defaultSelectText={value}
|
||||
disabled={true}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
|
||||
{/* ----------- Exchange rate ----------- */}
|
||||
<JournalExchangeRateInputField
|
||||
name={'exchange_rate'}
|
||||
formGroupProps={{ label: ' ', inline: true }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { MakeJournalProvider } from './MakeJournalProvider';
|
||||
*/
|
||||
export default function MakeJournalEntriesPage() {
|
||||
const { id: journalId } = useParams();
|
||||
|
||||
|
||||
return (
|
||||
<MakeJournalProvider journalId={journalId}>
|
||||
<MakeJournalEntriesForm />
|
||||
|
||||
@@ -21,15 +21,16 @@ export default function MakeJournalEntriesTable({
|
||||
entries,
|
||||
defaultEntry,
|
||||
error,
|
||||
initialLinesNumber = 4,
|
||||
minLinesNumber = 4,
|
||||
initialLinesNumber = 1,
|
||||
minLinesNumber = 1,
|
||||
currencyCode,
|
||||
}) {
|
||||
const { accounts, contacts } = useMakeJournalFormContext();
|
||||
const { accounts, contacts, branches } = useMakeJournalFormContext();
|
||||
|
||||
// Memorized data table columns.
|
||||
const columns = useJournalTableEntriesColumns();
|
||||
|
||||
|
||||
// Handles update datatable data.
|
||||
const handleUpdateData = (rowIndex, columnId, value) => {
|
||||
const newRows = compose(
|
||||
@@ -62,13 +63,13 @@ export default function MakeJournalEntriesTable({
|
||||
data={entries}
|
||||
sticky={true}
|
||||
totalRow={true}
|
||||
footer={true}
|
||||
payload={{
|
||||
accounts,
|
||||
errors: error,
|
||||
updateData: handleUpdateData,
|
||||
removeRow: handleRemoveRow,
|
||||
contacts,
|
||||
branches,
|
||||
autoFocus: ['account_id', 0],
|
||||
currencyCode,
|
||||
}}
|
||||
|
||||
@@ -1,44 +1,29 @@
|
||||
import React from 'react';
|
||||
import { FastField } from 'formik';
|
||||
import classNames from 'classnames';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { FormGroup, TextArea } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import { Postbox, ErrorMessage, Row, Col } from 'components';
|
||||
import Dragzone from 'components/Dragzone';
|
||||
import { inputIntent } from 'utils';
|
||||
import { Row, Col, Paper } from 'components';
|
||||
import { MakeJournalFormFooterLeft } from './MakeJournalFormFooterLeft';
|
||||
import { MakeJournalFormFooterRight } from './MakeJournalFormFooterRight';
|
||||
|
||||
export default function MakeJournalFormFooter() {
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
||||
<Postbox title={<T id={'journal_details'} />} defaultOpen={false}>
|
||||
<MakeJournalFooterPaper>
|
||||
<Row>
|
||||
<Col md={8}>
|
||||
<FastField name={'description'}>
|
||||
{({ field, meta: { error, touched } }) => (
|
||||
<FormGroup
|
||||
label={<T id={'description'} />}
|
||||
className={'form-group--description'}
|
||||
intent={inputIntent({ error, touched })}
|
||||
helperText={<ErrorMessage name="description" />}
|
||||
fill={true}
|
||||
>
|
||||
<TextArea fill={true} {...field} />
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
<MakeJournalFormFooterLeft />
|
||||
</Col>
|
||||
|
||||
<Col md={4}>
|
||||
<Dragzone
|
||||
initialFiles={[]}
|
||||
// onDrop={handleDropFiles}
|
||||
// onDeleteFile={handleDeleteFile}
|
||||
hint={<T id={'attachments_maximum'} />}
|
||||
/>
|
||||
<MakeJournalFormFooterRight />
|
||||
</Col>
|
||||
</Row>
|
||||
</Postbox>
|
||||
</MakeJournalFooterPaper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const MakeJournalFooterPaper = styled(Paper)`
|
||||
padding: 20px;
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FFormGroup, FEditableText, FormattedMessage as T } from 'components';
|
||||
|
||||
export function MakeJournalFormFooterLeft() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{/* --------- Description --------- */}
|
||||
<DescriptionFormGroup
|
||||
label={<T id={'description'} />}
|
||||
name={'description'}
|
||||
>
|
||||
<FEditableText
|
||||
name={'description'}
|
||||
placeholder={<T id={'make_jorunal.decscrption.placeholder'} />}
|
||||
/>
|
||||
</DescriptionFormGroup>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const DescriptionFormGroup = styled(FFormGroup)`
|
||||
&.bp3-form-group {
|
||||
.bp3-label {
|
||||
font-size: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.bp3-form-content {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
T,
|
||||
TotalLines,
|
||||
TotalLine,
|
||||
TotalLineBorderStyle,
|
||||
TotalLineTextStyle,
|
||||
} from 'components';
|
||||
import { useJournalTotals } from './utils';
|
||||
|
||||
export function MakeJournalFormFooterRight() {
|
||||
const { formattedSubtotal, formattedTotal } = useJournalTotals();
|
||||
|
||||
return (
|
||||
<MakeJouranlTotalLines>
|
||||
<TotalLine
|
||||
title={<T id={'make_journal.label.subtotal'} />}
|
||||
value={formattedSubtotal}
|
||||
borderStyle={TotalLineBorderStyle.None}
|
||||
/>
|
||||
<TotalLine
|
||||
title={<T id={'make_journal.label.total'} />}
|
||||
value={formattedTotal}
|
||||
textStyle={TotalLineTextStyle.Bold}
|
||||
/>
|
||||
</MakeJouranlTotalLines>
|
||||
);
|
||||
}
|
||||
|
||||
const MakeJouranlTotalLines = styled(TotalLines)`
|
||||
width: 100%;
|
||||
color: #555555;
|
||||
`;
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Button, Alignment, NavbarGroup, Classes } from '@blueprintjs/core';
|
||||
import styled from 'styled-components';
|
||||
import { useSetPrimaryBranchToForm } from './utils';
|
||||
import { useFeatureCan } from 'hooks/state';
|
||||
import {
|
||||
Icon,
|
||||
BranchSelect,
|
||||
FeatureCan,
|
||||
FormTopbar,
|
||||
DetailsBarSkeletonBase,
|
||||
} from 'components';
|
||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||
import { Features } from 'common';
|
||||
|
||||
/**
|
||||
* Make journal form topbar.
|
||||
* @returns
|
||||
*/
|
||||
export default function MakeJournalFormTopBar() {
|
||||
// Features guard.
|
||||
const { featureCan } = useFeatureCan();
|
||||
|
||||
// Sets the primary branch to form.
|
||||
useSetPrimaryBranchToForm();
|
||||
|
||||
// Can't display the navigation bar if branches feature is not enabled.
|
||||
if (!featureCan(Features.Branches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormTopbar>
|
||||
<NavbarGroup align={Alignment.LEFT}>
|
||||
<FeatureCan feature={Features.Branches}>
|
||||
<MakeJournalFormSelectBranch />
|
||||
</FeatureCan>
|
||||
</NavbarGroup>
|
||||
</FormTopbar>
|
||||
);
|
||||
}
|
||||
|
||||
function MakeJournalFormSelectBranch() {
|
||||
// Invoice form context.
|
||||
const { branches, isBranchesLoading } = useMakeJournalFormContext();
|
||||
|
||||
return isBranchesLoading ? (
|
||||
<DetailsBarSkeletonBase className={Classes.SKELETON} />
|
||||
) : (
|
||||
<BranchSelect
|
||||
name={'branch_id'}
|
||||
branches={branches}
|
||||
input={MakeJournalBranchSelectButton}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
function MakeJournalBranchSelectButton({ label }) {
|
||||
return (
|
||||
<Button
|
||||
text={intl.get('make_journal.branch_button.label', { label })}
|
||||
minimal={true}
|
||||
small={true}
|
||||
icon={<Icon icon={'branch-16'} iconSize={16} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import React, { createContext, useState } from 'react';
|
||||
import { isEqual, isUndefined } from 'lodash';
|
||||
import { Features } from 'common';
|
||||
import { useFeatureCan } from 'hooks/state';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import {
|
||||
useAccounts,
|
||||
@@ -8,7 +11,8 @@ import {
|
||||
useCreateJournal,
|
||||
useEditJournal,
|
||||
useSettings,
|
||||
useSettingsManualJournals
|
||||
useBranches,
|
||||
useSettingsManualJournals,
|
||||
} from 'hooks/query';
|
||||
|
||||
const MakeJournalFormContext = createContext();
|
||||
@@ -16,15 +20,17 @@ const MakeJournalFormContext = createContext();
|
||||
/**
|
||||
* Make journal form provider.
|
||||
*/
|
||||
function MakeJournalProvider({ journalId, ...props }) {
|
||||
function MakeJournalProvider({ journalId, query, ...props }) {
|
||||
// Features guard.
|
||||
const { featureCan } = useFeatureCan();
|
||||
const isBranchFeatureCan = featureCan(Features.Branches);
|
||||
|
||||
// Load the accounts list.
|
||||
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
|
||||
|
||||
// Load the customers list.
|
||||
const {
|
||||
data: contacts,
|
||||
isLoading: isContactsLoading,
|
||||
} = useAutoCompleteContacts();
|
||||
const { data: contacts, isLoading: isContactsLoading } =
|
||||
useAutoCompleteContacts();
|
||||
|
||||
// Load the currencies list.
|
||||
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
||||
@@ -43,15 +49,27 @@ function MakeJournalProvider({ journalId, ...props }) {
|
||||
// Loading the journal settings.
|
||||
const { isLoading: isSettingsLoading } = useSettingsManualJournals();
|
||||
|
||||
// Fetches the branches list.
|
||||
const {
|
||||
data: branches,
|
||||
isLoading: isBranchesLoading,
|
||||
isSuccess: isBranchesSuccess,
|
||||
} = useBranches(query, { enabled: isBranchFeatureCan });
|
||||
|
||||
// Submit form payload.
|
||||
const [submitPayload, setSubmitPayload] = useState({});
|
||||
|
||||
// Determines whether the warehouse and branches are loading.
|
||||
const isFeatureLoading = isBranchesLoading;
|
||||
|
||||
const provider = {
|
||||
accounts,
|
||||
contacts,
|
||||
currencies,
|
||||
manualJournal,
|
||||
|
||||
branches,
|
||||
|
||||
createJournalMutate,
|
||||
editJournalMutate,
|
||||
|
||||
@@ -59,12 +77,13 @@ function MakeJournalProvider({ journalId, ...props }) {
|
||||
isContactsLoading,
|
||||
isCurrenciesLoading,
|
||||
isJournalLoading,
|
||||
isFeatureLoading,
|
||||
isSettingsLoading,
|
||||
|
||||
isBranchesSuccess,
|
||||
isNewMode: !journalId,
|
||||
|
||||
submitPayload,
|
||||
setSubmitPayload
|
||||
setSubmitPayload,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -73,7 +92,7 @@ function MakeJournalProvider({ journalId, ...props }) {
|
||||
isJournalLoading ||
|
||||
isAccountsLoading ||
|
||||
isCurrenciesLoading ||
|
||||
isContactsLoading ||
|
||||
isContactsLoading ||
|
||||
isSettingsLoading
|
||||
}
|
||||
name={'make-journal-page'}
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Intent, Position, Button, Tooltip } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import { Icon, Money, Hint } from 'components';
|
||||
import { Menu, MenuItem, Position, Button } from '@blueprintjs/core';
|
||||
import { Popover2 } from '@blueprintjs/popover2';
|
||||
import { useFormikContext } from 'formik';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import {
|
||||
ExchangeRateInputGroup,
|
||||
Icon,
|
||||
Hint,
|
||||
FormattedMessage as T,
|
||||
} from 'components';
|
||||
import {
|
||||
AccountsListFieldCell,
|
||||
MoneyFieldCell,
|
||||
InputGroupCell,
|
||||
ContactsListFieldCell,
|
||||
BranchesListFieldCell,
|
||||
} from 'components/DataTableCells';
|
||||
import { safeSumBy } from 'utils';
|
||||
|
||||
import { CellType, Features, Align } from 'common';
|
||||
|
||||
import { useFeatureCan } from 'hooks/state';
|
||||
import { useCurrentOrganization } from 'hooks/state';
|
||||
import { useJournalIsForeign } from './utils';
|
||||
|
||||
/**
|
||||
* Contact header cell.
|
||||
@@ -40,42 +53,6 @@ export function DebitHeaderCell({ payload: { currencyCode } }) {
|
||||
return intl.get('debit_currency', { currency: currencyCode });
|
||||
}
|
||||
|
||||
/**
|
||||
* Account footer cell.
|
||||
*/
|
||||
function AccountFooterCell({ payload: { currencyCode } }) {
|
||||
return (
|
||||
<span>
|
||||
{intl.get('total_currency', { currency: currencyCode })}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Total credit table footer cell.
|
||||
*/
|
||||
function TotalCreditFooterCell({ payload: { currencyCode }, rows }) {
|
||||
const credit = safeSumBy(rows, 'original.credit');
|
||||
|
||||
return (
|
||||
<span>
|
||||
<Money amount={credit} currency={currencyCode} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Total debit table footer cell.
|
||||
*/
|
||||
function TotalDebitFooterCell({ payload: { currencyCode }, rows }) {
|
||||
const debit = safeSumBy(rows, 'original.debit');
|
||||
|
||||
return (
|
||||
<span>
|
||||
<Money amount={debit} currency={currencyCode} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Actions cell renderer.
|
||||
*/
|
||||
@@ -86,95 +63,123 @@ export const ActionsCellRenderer = ({
|
||||
data,
|
||||
payload,
|
||||
}) => {
|
||||
const onClickRemoveRole = () => {
|
||||
const handleClickRemoveRole = () => {
|
||||
payload.removeRow(index);
|
||||
};
|
||||
return (
|
||||
<Tooltip content={<T id={'remove_the_line'} />} position={Position.LEFT}>
|
||||
<Button
|
||||
icon={<Icon icon="times-circle" iconSize={14} />}
|
||||
iconSize={14}
|
||||
className="ml2"
|
||||
minimal={true}
|
||||
intent={Intent.DANGER}
|
||||
onClick={onClickRemoveRole}
|
||||
const exampleMenu = (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
onClick={handleClickRemoveRole}
|
||||
text={intl.get('make_journal.entries.remove_row')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Menu>
|
||||
);
|
||||
return (
|
||||
<Popover2 content={exampleMenu} placement="left-start">
|
||||
<Button
|
||||
icon={<Icon icon={'more-13'} iconSize={13} />}
|
||||
iconSize={14}
|
||||
className="m12"
|
||||
minimal={true}
|
||||
/>
|
||||
</Popover2>
|
||||
);
|
||||
};
|
||||
ActionsCellRenderer.cellType = CellType.Button;
|
||||
|
||||
/**
|
||||
* Retrieve columns of make journal entries table.
|
||||
*/
|
||||
export const useJournalTableEntriesColumns = () => {
|
||||
const { featureCan } = useFeatureCan();
|
||||
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: '#',
|
||||
accessor: 'index',
|
||||
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
|
||||
className: 'index',
|
||||
width: 40,
|
||||
disableResizing: true,
|
||||
disableSortBy: true,
|
||||
sticky: 'left',
|
||||
},
|
||||
{
|
||||
Header: intl.get('account'),
|
||||
id: 'account_id',
|
||||
accessor: 'account_id',
|
||||
Cell: AccountsListFieldCell,
|
||||
Footer: AccountFooterCell,
|
||||
className: 'account',
|
||||
disableSortBy: true,
|
||||
width: 160,
|
||||
fieldProps: { allowCreate: true }
|
||||
fieldProps: { allowCreate: true },
|
||||
},
|
||||
{
|
||||
Header: CreditHeaderCell,
|
||||
accessor: 'credit',
|
||||
Cell: MoneyFieldCell,
|
||||
Footer: TotalCreditFooterCell,
|
||||
className: 'credit',
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: DebitHeaderCell,
|
||||
accessor: 'debit',
|
||||
Cell: MoneyFieldCell,
|
||||
Footer: TotalDebitFooterCell,
|
||||
className: 'debit',
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: ContactHeaderCell,
|
||||
id: 'contact_id',
|
||||
accessor: 'contact_id',
|
||||
Cell: ContactsListFieldCell,
|
||||
className: 'contact',
|
||||
disableSortBy: true,
|
||||
width: 120,
|
||||
},
|
||||
...(featureCan(Features.Branches)
|
||||
? [
|
||||
{
|
||||
Header: intl.get('branch'),
|
||||
id: 'branch_id',
|
||||
accessor: 'branch_id',
|
||||
Cell: BranchesListFieldCell,
|
||||
disableSortBy: true,
|
||||
width: 120,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
Header: intl.get('note'),
|
||||
accessor: 'note',
|
||||
Cell: InputGroupCell,
|
||||
disableSortBy: true,
|
||||
className: 'note',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
Header: '',
|
||||
accessor: 'action',
|
||||
Cell: ActionsCellRenderer,
|
||||
className: 'actions',
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 45,
|
||||
align: Align.Center,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Journal exchange rate input field.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function JournalExchangeRateInputField({ ...props }) {
|
||||
const currentOrganization = useCurrentOrganization();
|
||||
const { values } = useFormikContext();
|
||||
|
||||
const isForeignJouranl = useJournalIsForeign();
|
||||
|
||||
// Can't continue if the customer is not foreign.
|
||||
if (!isForeignJouranl) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ExchangeRateInputGroup
|
||||
fromCurrency={values.currency_code}
|
||||
toCurrency={currentOrganization.base_currency}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||