Introduce React-Trello in place of React-Kanban
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -2,7 +2,7 @@ NGROK TEsting:
|
|||||||
./ngrok.exe http http://localhost:4000 -host-header="localhost:4000"
|
./ngrok.exe http http://localhost:4000 -host-header="localhost:4000"
|
||||||
|
|
||||||
Finding deadfiles - run from client directory
|
Finding deadfiles - run from client directory
|
||||||
npx deadfile ./src/index.js --exclude build templates
|
npx deadfile ./src/index.jsx --exclude build templates
|
||||||
|
|
||||||
#Crushing all hasura migrations by creating a new initialization from the server.
|
#Crushing all hasura migrations by creating a new initialization from the server.
|
||||||
hasura migrate create "Init" --from-server --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
hasura migrate create "Init" --from-server --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||||
@@ -11,4 +11,4 @@ Production-ImEXOnline!@#'
|
|||||||
hasura migrate status --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
hasura migrate status --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||||
|
|
||||||
Generate the license file:
|
Generate the license file:
|
||||||
$ generate-license-file --input package.json --output third-party-licenses.txt --overwrite
|
$ generate-license-file --input package.json --output third-party-licenses.txt --overwrite
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Clone Repository for:
|
|||||||
{
|
{
|
||||||
"name": "node-webhook-scripts",
|
"name": "node-webhook-scripts",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
"main": "index.jsx",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.16.4"
|
"express": "^4.16.4"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ module.exports = {
|
|||||||
|
|
||||||
{
|
{
|
||||||
name: "Bitbucket Webhook",
|
name: "Bitbucket Webhook",
|
||||||
script: "./webhook/index.js",
|
script: "./webhook/index.jsx",
|
||||||
env: {
|
env: {
|
||||||
NODE_ENV: "production"
|
NODE_ENV: "production"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="cypress" />
|
/// <reference types="cypress" />
|
||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
// This example plugins/index.js can be used to load plugins
|
// This example plugins/index.jsx can be used to load plugins
|
||||||
//
|
//
|
||||||
// You can change the location of this file or turn off loading
|
// You can change the location of this file or turn off loading
|
||||||
// the plugins file with the 'pluginsFile' configuration option.
|
// the plugins file with the 'pluginsFile' configuration option.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
// This example support/index.js is processed and
|
// This example support/index.jsx is processed and
|
||||||
// loaded automatically before your test files.
|
// loaded automatically before your test files.
|
||||||
//
|
//
|
||||||
// This is a great place to put global configuration and
|
// This is a great place to put global configuration and
|
||||||
|
|||||||
99
client/package-lock.json
generated
99
client/package-lock.json
generated
@@ -12,6 +12,7 @@
|
|||||||
"@ant-design/pro-layout": "^7.17.16",
|
"@ant-design/pro-layout": "^7.17.16",
|
||||||
"@apollo/client": "^3.8.10",
|
"@apollo/client": "^3.8.10",
|
||||||
"@asseinfo/react-kanban": "^2.2.0",
|
"@asseinfo/react-kanban": "^2.2.0",
|
||||||
|
"@emotion/is-prop-valid": "^1.2.2",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.2.2",
|
"@fingerprintjs/fingerprintjs": "^4.2.2",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.2.1",
|
"@reduxjs/toolkit": "^2.2.1",
|
||||||
@@ -23,7 +24,9 @@
|
|||||||
"antd": "^5.15.3",
|
"antd": "^5.15.3",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
"apollo-link-sentry": "^3.3.0",
|
"apollo-link-sentry": "^3.3.0",
|
||||||
|
"autosize": "^6.0.1",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"dayjs-business-days2": "^1.2.2",
|
"dayjs-business-days2": "^1.2.2",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
@@ -34,6 +37,8 @@
|
|||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"i18next": "^23.10.0",
|
"i18next": "^23.10.0",
|
||||||
"i18next-browser-languagedetector": "^7.0.2",
|
"i18next-browser-languagedetector": "^7.0.2",
|
||||||
|
"immutability-helper": "^3.1.1",
|
||||||
|
"kuika-smooth-dnd": "^1.0.0",
|
||||||
"libphonenumber-js": "^1.10.57",
|
"libphonenumber-js": "^1.10.57",
|
||||||
"logrocket": "^8.0.1",
|
"logrocket": "^8.0.1",
|
||||||
"markerjs2": "^2.32.0",
|
"markerjs2": "^2.32.0",
|
||||||
@@ -54,6 +59,7 @@
|
|||||||
"react-joyride": "^2.7.4",
|
"react-joyride": "^2.7.4",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-number-format": "^5.3.3",
|
"react-number-format": "^5.3.3",
|
||||||
|
"react-popopo": "^2.1.9",
|
||||||
"react-product-fruits": "^2.2.6",
|
"react-product-fruits": "^2.2.6",
|
||||||
"react-redux": "^9.1.0",
|
"react-redux": "^9.1.0",
|
||||||
"react-resizable": "^3.0.5",
|
"react-resizable": "^3.0.5",
|
||||||
@@ -63,6 +69,7 @@
|
|||||||
"react-virtualized": "^9.22.5",
|
"react-virtualized": "^9.22.5",
|
||||||
"recharts": "^2.12.2",
|
"recharts": "^2.12.2",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
|
"redux-actions": "^2.6.5",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.3.0",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
@@ -2660,8 +2667,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/is-prop-valid": {
|
"node_modules/@emotion/is-prop-valid": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.2",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/memoize": "^0.8.1"
|
"@emotion/memoize": "^0.8.1"
|
||||||
}
|
}
|
||||||
@@ -8151,6 +8159,11 @@
|
|||||||
"postcss": "^8.1.0"
|
"postcss": "^8.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/autosize": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/autosize/-/autosize-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ=="
|
||||||
|
},
|
||||||
"node_modules/available-typed-arrays": {
|
"node_modules/available-typed-arrays": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -9396,7 +9409,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/classnames": {
|
"node_modules/classnames": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
|
||||||
},
|
},
|
||||||
"node_modules/clean-css": {
|
"node_modules/clean-css": {
|
||||||
"version": "5.3.3",
|
"version": "5.3.3",
|
||||||
@@ -14416,6 +14430,11 @@
|
|||||||
"url": "https://opencollective.com/immer"
|
"url": "https://opencollective.com/immer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immutability-helper": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/immutability-helper/-/immutability-helper-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-Q0QaXjPjwIju/28TsugCHNEASwoCcJSyJV3uO1sOIQGI0jKgm9f41Lvz0DZj3n46cNCyAZTsEYoY4C2bVRUzyQ=="
|
||||||
|
},
|
||||||
"node_modules/immutable": {
|
"node_modules/immutable": {
|
||||||
"version": "4.3.5",
|
"version": "4.3.5",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
@@ -17464,6 +17483,11 @@
|
|||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/just-curry-it": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/just-curry-it/-/just-curry-it-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-Q8206k8pTY7krW32cdmPsP+DqqLgWx/hYPSj9/+7SYqSqz7UuwPbfSe07lQtvuuaVyiSJveXk0E5RydOuWwsEg=="
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -17492,6 +17516,11 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/kuika-smooth-dnd": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/kuika-smooth-dnd/-/kuika-smooth-dnd-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-bNv7SBo9IB+ovMmBMYw9IS24f7B8Mek5uO+E4cGKhUjthIquxsIIszmOcdvbF+8t+2GAvTsqW6lsHSmd3Ry6/Q=="
|
||||||
|
},
|
||||||
"node_modules/kuler": {
|
"node_modules/kuler": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -22472,6 +22501,24 @@
|
|||||||
"react-dom": ">=16.3.0"
|
"react-dom": ">=16.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-popopo": {
|
||||||
|
"version": "2.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-popopo/-/react-popopo-2.1.9.tgz",
|
||||||
|
"integrity": "sha512-zXOpcLSpaLZmBxhdtenJzQPLjY81XknVS/tXH4Kv5BBrnYIUPHvVdGmS7+o9s7DjCzzdK7AdVwtG+FVSO0cZ8g==",
|
||||||
|
"dependencies": {
|
||||||
|
"classnames": ">= 2.0",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react": ">= 16.3",
|
||||||
|
"react-dom": ">= 16.3",
|
||||||
|
"styled-components": ">= 4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"classnames": ">= 2.0",
|
||||||
|
"react": ">= 16.3",
|
||||||
|
"react-dom": ">= 16.3",
|
||||||
|
"styled-components": ">= 4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-product-fruits": {
|
"node_modules/react-product-fruits": {
|
||||||
"version": "2.2.6",
|
"version": "2.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/react-product-fruits/-/react-product-fruits-2.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-product-fruits/-/react-product-fruits-2.2.6.tgz",
|
||||||
@@ -22860,10 +22907,27 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reduce-reducers": {
|
||||||
|
"version": "0.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/reduce-reducers/-/reduce-reducers-0.4.3.tgz",
|
||||||
|
"integrity": "sha512-+CNMnI8QhgVMtAt54uQs3kUxC3Sybpa7Y63HR14uGLgI9/QR5ggHvpxwhGGe3wmx5V91YwqQIblN9k5lspAmGw=="
|
||||||
|
},
|
||||||
"node_modules/redux": {
|
"node_modules/redux": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/redux-actions": {
|
||||||
|
"version": "2.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-actions/-/redux-actions-2.6.5.tgz",
|
||||||
|
"integrity": "sha512-pFhEcWFTYNk7DhQgxMGnbsB1H2glqhQJRQrtPb96kD3hWiZRzXHwwmFPswg6V2MjraXRXWNmuP9P84tvdLAJmw==",
|
||||||
|
"dependencies": {
|
||||||
|
"invariant": "^2.2.4",
|
||||||
|
"just-curry-it": "^3.1.0",
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"reduce-reducers": "^0.4.3",
|
||||||
|
"to-camel-case": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/redux-logger": {
|
"node_modules/redux-logger": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -24779,6 +24843,14 @@
|
|||||||
"react-dom": ">= 16.8.0"
|
"react-dom": ">= 16.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/styled-components/node_modules/@emotion/is-prop-valid": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/memoize": "^0.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/styled-components/node_modules/@emotion/unitless": {
|
"node_modules/styled-components/node_modules/@emotion/unitless": {
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
@@ -25416,6 +25488,14 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/to-camel-case": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/to-camel-case/-/to-camel-case-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-nD8pQi5H34kyu1QDMFjzEIYqk0xa9Alt6ZfrdEMuHCFOfTLhDG5pgTu/aAM9Wt9lXILwlXmWP43b8sav0GNE8Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"to-space-case": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/to-fast-properties": {
|
"node_modules/to-fast-properties": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -25423,6 +25503,11 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/to-no-case": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg=="
|
||||||
|
},
|
||||||
"node_modules/to-readable-stream": {
|
"node_modules/to-readable-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -25441,6 +25526,14 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/to-space-case": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/to-space-case/-/to-space-case-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA==",
|
||||||
|
"dependencies": {
|
||||||
|
"to-no-case": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/toggle-selection": {
|
"node_modules/toggle-selection": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"@ant-design/pro-layout": "^7.17.16",
|
"@ant-design/pro-layout": "^7.17.16",
|
||||||
"@apollo/client": "^3.8.10",
|
"@apollo/client": "^3.8.10",
|
||||||
"@asseinfo/react-kanban": "^2.2.0",
|
"@asseinfo/react-kanban": "^2.2.0",
|
||||||
|
"@emotion/is-prop-valid": "^1.2.2",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.2.2",
|
"@fingerprintjs/fingerprintjs": "^4.2.2",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.2.1",
|
"@reduxjs/toolkit": "^2.2.1",
|
||||||
@@ -23,7 +24,9 @@
|
|||||||
"antd": "^5.15.3",
|
"antd": "^5.15.3",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
"apollo-link-sentry": "^3.3.0",
|
"apollo-link-sentry": "^3.3.0",
|
||||||
|
"autosize": "^6.0.1",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"dayjs-business-days2": "^1.2.2",
|
"dayjs-business-days2": "^1.2.2",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
@@ -34,6 +37,8 @@
|
|||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"i18next": "^23.10.0",
|
"i18next": "^23.10.0",
|
||||||
"i18next-browser-languagedetector": "^7.0.2",
|
"i18next-browser-languagedetector": "^7.0.2",
|
||||||
|
"immutability-helper": "^3.1.1",
|
||||||
|
"kuika-smooth-dnd": "^1.0.0",
|
||||||
"libphonenumber-js": "^1.10.57",
|
"libphonenumber-js": "^1.10.57",
|
||||||
"logrocket": "^8.0.1",
|
"logrocket": "^8.0.1",
|
||||||
"markerjs2": "^2.32.0",
|
"markerjs2": "^2.32.0",
|
||||||
@@ -54,6 +59,7 @@
|
|||||||
"react-joyride": "^2.7.4",
|
"react-joyride": "^2.7.4",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-number-format": "^5.3.3",
|
"react-number-format": "^5.3.3",
|
||||||
|
"react-popopo": "^2.1.9",
|
||||||
"react-product-fruits": "^2.2.6",
|
"react-product-fruits": "^2.2.6",
|
||||||
"react-redux": "^9.1.0",
|
"react-redux": "^9.1.0",
|
||||||
"react-resizable": "^3.0.5",
|
"react-resizable": "^3.0.5",
|
||||||
@@ -63,6 +69,7 @@
|
|||||||
"react-virtualized": "^9.22.5",
|
"react-virtualized": "^9.22.5",
|
||||||
"recharts": "^2.12.2",
|
"recharts": "^2.12.2",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
|
"redux-actions": "^2.6.5",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.3.0",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
|
|||||||
@@ -16422,7 +16422,7 @@ For when you don't want to write the same thing over and over to cache a method
|
|||||||
$ npm install --save-dev stubs
|
$ npm install --save-dev stubs
|
||||||
```
|
```
|
||||||
```js
|
```js
|
||||||
var mylib = require('./lib/index.js')
|
var mylib = require('./lib/index.jsx')
|
||||||
var stubs = require('stubs')
|
var stubs = require('stubs')
|
||||||
|
|
||||||
// make it a noop
|
// make it a noop
|
||||||
|
|||||||
@@ -16567,7 +16567,7 @@ even more slower.
|
|||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ node benchmarks/index.js
|
$ node benchmarks/index.jsx
|
||||||
Benchmarking: sign
|
Benchmarking: sign
|
||||||
elliptic#sign x 262 ops/sec ±0.51% (177 runs sampled)
|
elliptic#sign x 262 ops/sec ±0.51% (177 runs sampled)
|
||||||
eccjs#sign x 55.91 ops/sec ±0.90% (144 runs sampled)
|
eccjs#sign x 55.91 ops/sec ±0.90% (144 runs sampled)
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
|||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import { setJoyRideSteps } from "../../redux/application/application.actions";
|
import { setJoyRideSteps } from "../../redux/application/application.actions";
|
||||||
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
|
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
|
|||||||
@@ -40,55 +40,60 @@ function getContrastYIQ(bgColor) {
|
|||||||
return yiq >= 128 ? "black" : "white";
|
return yiq >= 128 ? "black" : "white";
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ProductionBoardCard(technician, card, bodyshop, cardSettings) {
|
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
let employee_body, employee_prep, employee_refinish, employee_csr;
|
let employee_body, employee_prep, employee_refinish, employee_csr;
|
||||||
if (card.employee_body) {
|
|
||||||
employee_body = bodyshop.employees.find((e) => e.id === card.employee_body);
|
if (card && card.metadata && card.metadata.employee_body) {
|
||||||
|
employee_body = bodyshop.employees.find((e) => e.id === card.metadata.employee_body);
|
||||||
}
|
}
|
||||||
if (card.employee_prep) {
|
if (card && card.metadata && card.metadata.employee_prep) {
|
||||||
employee_prep = bodyshop.employees.find((e) => e.id === card.employee_prep);
|
employee_prep = bodyshop.employees.find((e) => e.id === card.metadata.employee_prep);
|
||||||
}
|
}
|
||||||
if (card.employee_refinish) {
|
if (card && card.metadata && card.metadata.employee_refinish) {
|
||||||
employee_refinish = bodyshop.employees.find((e) => e.id === card.employee_refinish);
|
employee_refinish = bodyshop.employees.find((e) => e.id === card.metadata.employee_refinish);
|
||||||
}
|
}
|
||||||
if (card.employee_csr) {
|
if (card && card.metadata && card.metadata.employee_csr) {
|
||||||
employee_csr = bodyshop.employees.find((e) => e.id === card.employee_csr);
|
employee_csr = bodyshop.employees.find((e) => e.id === card.metadata.employee_csr);
|
||||||
}
|
}
|
||||||
// if (card.employee_csr) {
|
// if (card && card.metadata && card.metadata.employee_csr) {
|
||||||
// employee_csr = bodyshop.employees.find((e) => e.id === card.employee_csr);
|
// employee_csr = bodyshop.employees.find((e) => e.id === card.metadata.employee_csr);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const pastDueAlert =
|
const pastDueAlert =
|
||||||
!!card.scheduled_completion &&
|
!!card?.metadata?.scheduled_completion &&
|
||||||
((dayjs().isSameOrAfter(dayjs(card.scheduled_completion), "day") && "production-completion-past") ||
|
((dayjs().isSameOrAfter(dayjs(card.metadata.scheduled_completion), "day") && "production-completion-past") ||
|
||||||
(dayjs().add(1, "day").isSame(dayjs(card.scheduled_completion), "day") && "production-completion-soon"));
|
(dayjs().add(1, "day").isSame(dayjs(card.metadata.scheduled_completion), "day") && "production-completion-soon"));
|
||||||
|
|
||||||
|
const totalHrs = card
|
||||||
|
? card.metadata.labhrs.aggregate.sum.mod_lb_hrs + card.metadata.larhrs.aggregate.sum.mod_lb_hrs
|
||||||
|
: 0;
|
||||||
|
|
||||||
const totalHrs = card.labhrs.aggregate.sum.mod_lb_hrs + card.larhrs.aggregate.sum.mod_lb_hrs;
|
|
||||||
const bgColor = cardColor(bodyshop.ssbuckets, totalHrs);
|
const bgColor = cardColor(bodyshop.ssbuckets, totalHrs);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className="react-kanban-card imex-kanban-card"
|
|
||||||
size="small"
|
size="small"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
cardSettings && cardSettings.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
|
cardSettings && cardSettings.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
|
||||||
color: cardSettings && cardSettings.cardcolor && getContrastYIQ(bgColor)
|
color: cardSettings && cardSettings.cardcolor && getContrastYIQ(bgColor),
|
||||||
|
maxWidth: "250px",
|
||||||
|
marginBottom: "5px"
|
||||||
}}
|
}}
|
||||||
title={
|
title={
|
||||||
<Space>
|
<Space>
|
||||||
<ProductionAlert record={card} key="alert" />
|
<ProductionAlert record={card} key="alert" />
|
||||||
{card.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
|
{card.metadata.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
|
||||||
{card.iouparent && (
|
{card.metadata.iouparent && (
|
||||||
<Tooltip title={t("jobs.labels.iou")}>
|
<Tooltip title={t("jobs.labels.iou")}>
|
||||||
<BranchesOutlined style={{ color: "orangered" }} />
|
<BranchesOutlined style={{ color: "orangered" }} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<span style={{ fontWeight: "bolder" }}>
|
<span style={{ fontWeight: "bolder" }}>
|
||||||
<Link to={technician ? `/tech/joblookup?selected=${card.id}` : `/manage/jobs/${card.id}`}>
|
<Link to={technician ? `/tech/joblookup?selected=${card.id}` : `/manage/jobs/${card.id}`}>
|
||||||
{card.ro_number || t("general.labels.na")}
|
{card.metadata.ro_number || t("general.labels.na")}
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -103,7 +108,7 @@ export default function ProductionBoardCard(technician, card, bodyshop, cardSett
|
|||||||
{cardSettings && cardSettings.ownr_nm && (
|
{cardSettings && cardSettings.ownr_nm && (
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
{cardSettings && cardSettings.compact ? (
|
{cardSettings && cardSettings.compact ? (
|
||||||
<div className="ellipses">{`${card.ownr_ln || ""} ${card.ownr_co_nm || ""}`}</div>
|
<div className="ellipses">{`${card.metadata.ownr_ln || ""} ${card.metadata.ownr_co_nm || ""}`}</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="ellipses">
|
<div className="ellipses">
|
||||||
<OwnerNameDisplay ownerObject={card} />
|
<OwnerNameDisplay ownerObject={card} />
|
||||||
@@ -112,18 +117,18 @@ export default function ProductionBoardCard(technician, card, bodyshop, cardSett
|
|||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<div className="ellipses">{`${card.v_model_yr || ""} ${
|
<div className="ellipses">{`${card.metadata.v_model_yr || ""} ${
|
||||||
card.v_make_desc || ""
|
card.metadata.v_make_desc || ""
|
||||||
} ${card.v_model_desc || ""}`}</div>
|
} ${card.metadata.v_model_desc || ""}`}</div>
|
||||||
</Col>
|
</Col>
|
||||||
{cardSettings && cardSettings.ins_co_nm && card.ins_co_nm && (
|
{cardSettings && cardSettings.ins_co_nm && card.metadata.ins_co_nm && (
|
||||||
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
|
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
|
||||||
<div className="ellipses">{card.ins_co_nm || ""}</div>
|
<div className="ellipses">{card.metadata.ins_co_nm || ""}</div>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
{cardSettings && cardSettings.clm_no && card.clm_no && (
|
{cardSettings && cardSettings.clm_no && card.metadata.clm_no && (
|
||||||
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
|
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
|
||||||
<div className="ellipses">{card.clm_no || ""}</div>
|
<div className="ellipses">{card.metadata.clm_no || ""}</div>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -132,7 +137,7 @@ export default function ProductionBoardCard(technician, card, bodyshop, cardSett
|
|||||||
<Row>
|
<Row>
|
||||||
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`B: ${
|
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`B: ${
|
||||||
employee_body ? `${employee_body.first_name.substr(0, 3)} ${employee_body.last_name.charAt(0)}` : ""
|
employee_body ? `${employee_body.first_name.substr(0, 3)} ${employee_body.last_name.charAt(0)}` : ""
|
||||||
} ${card.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</Col>
|
} ${card.metadata.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</Col>
|
||||||
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`P: ${
|
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`P: ${
|
||||||
employee_prep ? `${employee_prep.first_name.substr(0, 3)} ${employee_prep.last_name.charAt(0)}` : ""
|
employee_prep ? `${employee_prep.first_name.substr(0, 3)} ${employee_prep.last_name.charAt(0)}` : ""
|
||||||
}`}</Col>
|
}`}</Col>
|
||||||
@@ -140,7 +145,7 @@ export default function ProductionBoardCard(technician, card, bodyshop, cardSett
|
|||||||
employee_refinish
|
employee_refinish
|
||||||
? `${employee_refinish.first_name.substr(0, 3)} ${employee_refinish.last_name.charAt(0)}`
|
? `${employee_refinish.first_name.substr(0, 3)} ${employee_refinish.last_name.charAt(0)}`
|
||||||
: ""
|
: ""
|
||||||
} ${card.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</Col>
|
} ${card.metadata.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</Col>
|
||||||
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`C: ${
|
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`C: ${
|
||||||
employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""
|
employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""
|
||||||
}`}</Col>
|
}`}</Col>
|
||||||
@@ -151,38 +156,38 @@ export default function ProductionBoardCard(technician, card, bodyshop, cardSett
|
|||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`B: ${
|
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`B: ${
|
||||||
card.labhrs.aggregate.sum.mod_lb_hrs || "?"
|
card.metadata.labhrs.aggregate.sum.mod_lb_hrs || "?"
|
||||||
} hrs`}</Col>
|
} hrs`}</Col>
|
||||||
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`R: ${
|
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`R: ${
|
||||||
card.larhrs.aggregate.sum.mod_lb_hrs || "?"
|
card.metadata.larhrs.aggregate.sum.mod_lb_hrs || "?"
|
||||||
} hrs`}</Col>
|
} hrs`}</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
)} */}
|
)} */}
|
||||||
{cardSettings && cardSettings.actual_in && card.actual_in && (
|
{cardSettings && cardSettings.actual_in && card.metadata.actual_in && (
|
||||||
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
|
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
|
||||||
<Space>
|
<Space>
|
||||||
<DownloadOutlined />
|
<DownloadOutlined />
|
||||||
<DateTimeFormatter format="MM/DD">{card.actual_in}</DateTimeFormatter>
|
<DateTimeFormatter format="MM/DD">{card.metadata.actual_in}</DateTimeFormatter>
|
||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
{cardSettings && cardSettings.scheduled_completion && card.scheduled_completion && (
|
{cardSettings && cardSettings.scheduled_completion && card.metadata.scheduled_completion && (
|
||||||
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
|
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
|
||||||
<Space className={pastDueAlert}>
|
<Space className={pastDueAlert}>
|
||||||
<CalendarOutlined />
|
<CalendarOutlined />
|
||||||
<DateTimeFormatter format="MM/DD">{card.scheduled_completion}</DateTimeFormatter>
|
<DateTimeFormatter format="MM/DD">{card.metadata.scheduled_completion}</DateTimeFormatter>
|
||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
{cardSettings && cardSettings.ats && card.alt_transport && (
|
{cardSettings && cardSettings.ats && card.metadata.alt_transport && (
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<div>{card.alt_transport || ""}</div>
|
<div>{card.metadata.alt_transport || ""}</div>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
{cardSettings && cardSettings.sublets && (
|
{cardSettings && cardSettings.sublets && (
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<ProductionSubletsManageComponent subletJobLines={card.subletLines} />
|
<ProductionSubletsManageComponent subletJobLines={card.metadata.subletLines} />
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
{cardSettings && cardSettings.production_note && (
|
{cardSettings && cardSettings.production_note && (
|
||||||
@@ -192,7 +197,7 @@ export default function ProductionBoardCard(technician, card, bodyshop, cardSett
|
|||||||
)}
|
)}
|
||||||
{cardSettings && cardSettings.partsstatus && (
|
{cardSettings && cardSettings.partsstatus && (
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<JobPartsQueueCount parts={card.joblines_status} />
|
<JobPartsQueueCount parts={card.metadata.joblines_status} />
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { useApolloClient } from "@apollo/client";
|
import { useApolloClient } from "@apollo/client";
|
||||||
import Board, { moveCard } from "@asseinfo/react-kanban";
|
import Board from "../../components/trello-board/index";
|
||||||
import { Button, Grid, notification, Space, Statistic } from "antd";
|
import { Button, Grid, notification, Space, Statistic } from "antd";
|
||||||
import { PageHeader } from "@ant-design/pro-layout";
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
@@ -42,9 +42,7 @@ export function ProductionBoardKanbanComponent({
|
|||||||
insertAuditTrail,
|
insertAuditTrail,
|
||||||
associationSettings
|
associationSettings
|
||||||
}) {
|
}) {
|
||||||
const [boardLanes, setBoardLanes] = useState({
|
const [boardLanes, setBoardLanes] = useState({ lanes: [] });
|
||||||
columns: [{ id: "Loading...", title: "Loading...", cards: [] }]
|
|
||||||
});
|
|
||||||
|
|
||||||
const [filter, setFilter] = useState({ search: "", employeeId: null });
|
const [filter, setFilter] = useState({ search: "", employeeId: null });
|
||||||
|
|
||||||
@@ -58,7 +56,7 @@ export function ProductionBoardKanbanComponent({
|
|||||||
filter
|
filter
|
||||||
);
|
);
|
||||||
|
|
||||||
boardData.columns = boardData.columns.map((d) => {
|
boardData.lanes = boardData.lanes.map((d) => {
|
||||||
return { ...d, title: `${d.title} (${d.cards.length})` };
|
return { ...d, title: `${d.title} (${d.cards.length})` };
|
||||||
});
|
});
|
||||||
setBoardLanes(boardData);
|
setBoardLanes(boardData);
|
||||||
@@ -67,63 +65,55 @@ export function ProductionBoardKanbanComponent({
|
|||||||
|
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
|
|
||||||
const handleDragEnd = async (card, source, destination) => {
|
const handleDragEnd = async (cardId, sourceLaneId, targetLaneId, position, cardDetails) => {
|
||||||
logImEXEvent("kanban_drag_end");
|
logImEXEvent("kanban_drag_end");
|
||||||
|
|
||||||
setIsMoving(true);
|
setIsMoving(true);
|
||||||
setBoardLanes(moveCard(boardLanes, source, destination));
|
|
||||||
const sameColumnTransfer = source.fromColumnId === destination.toColumnId;
|
|
||||||
const sourceColumn = boardLanes.columns.find((x) => x.id === source.fromColumnId);
|
|
||||||
const destinationColumn = boardLanes.columns.find((x) => x.id === destination.toColumnId);
|
|
||||||
|
|
||||||
const movedCardWillBeFirst = destination.toPosition === 0;
|
const sameColumnTransfer = sourceLaneId === targetLaneId;
|
||||||
|
const sourceLane = boardLanes.lanes.find((lane) => lane.id === sourceLaneId);
|
||||||
|
const targetLane = boardLanes.lanes.find((lane) => lane.id === targetLaneId);
|
||||||
|
|
||||||
const movedCardWillBeLast = destinationColumn.cards.length - destination.toPosition < 1;
|
const movedCardWillBeFirst = position === 0;
|
||||||
|
const movedCardWillBeLast = targetLane.cards.length - position < 1;
|
||||||
|
|
||||||
const lastCardInDestinationColumn = destinationColumn.cards[destinationColumn.cards.length - 1];
|
const lastCardInTargetLane = targetLane.cards[targetLane.cards.length - 1];
|
||||||
|
|
||||||
const oldChildCard = sourceColumn.cards[source.fromPosition + 1];
|
const oldChildCard = sourceLane.cards[position + 1];
|
||||||
|
|
||||||
const newChildCard = movedCardWillBeLast
|
const newChildCard = movedCardWillBeLast
|
||||||
? null
|
? null
|
||||||
: destinationColumn.cards[
|
: targetLane.cards[sameColumnTransfer ? (position - position > 0 ? position : position + 1) : position];
|
||||||
sameColumnTransfer
|
|
||||||
? source.fromPosition - destination.toPosition > 0
|
|
||||||
? destination.toPosition
|
|
||||||
: destination.toPosition + 1
|
|
||||||
: destination.toPosition
|
|
||||||
];
|
|
||||||
|
|
||||||
const oldChildCardNewParent = oldChildCard ? card.kanbanparent : null;
|
const oldChildCardNewParent = oldChildCard ? cardDetails.kanbanparent : null;
|
||||||
|
|
||||||
let movedCardNewKanbanParent;
|
let movedCardNewKanbanParent;
|
||||||
if (movedCardWillBeFirst) {
|
if (movedCardWillBeFirst) {
|
||||||
//console.log("==> New Card is first.");
|
|
||||||
movedCardNewKanbanParent = "-1";
|
movedCardNewKanbanParent = "-1";
|
||||||
} else if (movedCardWillBeLast) {
|
} else if (movedCardWillBeLast) {
|
||||||
// console.log("==> New Card is last.");
|
movedCardNewKanbanParent = lastCardInTargetLane.id;
|
||||||
movedCardNewKanbanParent = lastCardInDestinationColumn.id;
|
|
||||||
} else if (!!newChildCard) {
|
} else if (!!newChildCard) {
|
||||||
// console.log("==> New Card is somewhere in the middle");
|
|
||||||
movedCardNewKanbanParent = newChildCard.kanbanparent;
|
movedCardNewKanbanParent = newChildCard.kanbanparent;
|
||||||
} else {
|
} else {
|
||||||
console.log("==> !!!!!!Couldn't find a parent.!!!! <==");
|
console.log("==> !!!!!!Couldn't find a parent.!!!! <==");
|
||||||
}
|
}
|
||||||
const newChildCardNewParent = newChildCard ? card.id : null;
|
const newChildCardNewParent = newChildCard ? cardId : null;
|
||||||
|
|
||||||
const update = await client.mutate({
|
const update = await client.mutate({
|
||||||
mutation: generate_UPDATE_JOB_KANBAN(
|
mutation: generate_UPDATE_JOB_KANBAN(
|
||||||
oldChildCard ? oldChildCard.id : null,
|
oldChildCard ? oldChildCard.id : null,
|
||||||
oldChildCardNewParent,
|
oldChildCardNewParent,
|
||||||
card.id,
|
cardId,
|
||||||
movedCardNewKanbanParent,
|
movedCardNewKanbanParent,
|
||||||
destination.toColumnId,
|
targetLaneId,
|
||||||
newChildCard ? newChildCard.id : null,
|
newChildCard ? newChildCard.id : null,
|
||||||
newChildCardNewParent
|
newChildCardNewParent
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: card.id,
|
jobid: cardId,
|
||||||
operation: AuditTrailMapping.jobstatuschange(destination.toColumnId),
|
operation: AuditTrailMapping.jobstatuschange(targetLaneId),
|
||||||
type: "jobstatuschange"
|
type: "jobstatuschange"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -134,6 +124,8 @@ export function ProductionBoardKanbanComponent({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsMoving(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const totalHrs = data
|
const totalHrs = data
|
||||||
@@ -214,7 +206,6 @@ export function ProductionBoardKanbanComponent({
|
|||||||
return (
|
return (
|
||||||
<Container width={width}>
|
<Container width={width}>
|
||||||
<IndefiniteLoading loading={isMoving} />
|
<IndefiniteLoading loading={isMoving} />
|
||||||
|
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={
|
title={
|
||||||
<Space>
|
<Space>
|
||||||
@@ -234,18 +225,20 @@ export function ProductionBoardKanbanComponent({
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{cardSettings.cardcolor && <CardColorLegend cardSettings={cardSettings} bodyshop={bodyshop} />}
|
{cardSettings.cardcolor && <CardColorLegend cardSettings={cardSettings} bodyshop={bodyshop} />}
|
||||||
|
|
||||||
<ProductionListDetailComponent jobs={data} />
|
<ProductionListDetailComponent jobs={data} />
|
||||||
<StickyContainer>
|
<StickyContainer>
|
||||||
<Board
|
<Board
|
||||||
style={{ height: "100%" }}
|
data={boardLanes}
|
||||||
children={boardLanes}
|
draggable
|
||||||
disableCardDrag={isMoving}
|
canAddLanes
|
||||||
{...(cardSettings.stickyheader && stickyHeader)}
|
handleDragEnd={handleDragEnd}
|
||||||
renderCard={(card) => ProductionBoardCard(technician, card, bodyshop, cardSettings)}
|
editable
|
||||||
onCardDragEnd={handleDragEnd}
|
style={{ height: "100%", backgroundColor: "transparent" }}
|
||||||
|
renameLane
|
||||||
|
components={{
|
||||||
|
Card: (cardProps) => ProductionBoardCard({ card: cardProps, technician, bodyshop, cardSettings })
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</StickyContainer>
|
</StickyContainer>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ const sortByParentId = (arr) => {
|
|||||||
//console.log("sortByParentId -> byParentsIdsList", byParentsIdsList);
|
//console.log("sortByParentId -> byParentsIdsList", byParentsIdsList);
|
||||||
|
|
||||||
while (byParentsIdsList[parentId]) {
|
while (byParentsIdsList[parentId]) {
|
||||||
sortedList.push(...byParentsIdsList[parentId]); //Spread in the whole list in case several items have the same parents.
|
sortedList.push(...byParentsIdsList[parentId]); //Spread in the whole list in case several items have the same parents.
|
||||||
parentId = byParentsIdsList[parentId][byParentsIdsList[parentId].length -1].id; //Grab the ID from the last one.
|
parentId = byParentsIdsList[parentId][byParentsIdsList[parentId].length - 1].id; //Grab the ID from the last one.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (byParentsIdsList["null"]) byParentsIdsList["null"].map((i) => sortedList.push(i));
|
if (byParentsIdsList["null"]) byParentsIdsList["null"].map((i) => sortedList.push(i));
|
||||||
@@ -40,15 +40,13 @@ const sortByParentId = (arr) => {
|
|||||||
|
|
||||||
export const createBoardData = (AllStatuses, Jobs, filter) => {
|
export const createBoardData = (AllStatuses, Jobs, filter) => {
|
||||||
const { search, employeeId } = filter;
|
const { search, employeeId } = filter;
|
||||||
const boardLanes = {
|
const lanes = AllStatuses.map((s) => {
|
||||||
columns: AllStatuses.map((s) => {
|
return {
|
||||||
return {
|
id: s,
|
||||||
id: s,
|
title: s,
|
||||||
title: s,
|
cards: []
|
||||||
cards: []
|
};
|
||||||
};
|
});
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredJobs =
|
const filteredJobs =
|
||||||
(search === "" || !search) && !employeeId
|
(search === "" || !search) && !employeeId
|
||||||
@@ -75,16 +73,25 @@ export const createBoardData = (AllStatuses, Jobs, filter) => {
|
|||||||
|
|
||||||
Object.keys(DataGroupedByStatus).map((statusGroupKey) => {
|
Object.keys(DataGroupedByStatus).map((statusGroupKey) => {
|
||||||
try {
|
try {
|
||||||
const needle = boardLanes.columns.find((l) => l.id === statusGroupKey);
|
const lane = lanes.find((l) => l.id === statusGroupKey);
|
||||||
if (!needle?.cards) return null;
|
if (!lane?.cards) return null;
|
||||||
needle.cards = sortByParentId(DataGroupedByStatus[statusGroupKey]);
|
lane.cards = sortByParentId(DataGroupedByStatus[statusGroupKey]).map((job) => {
|
||||||
|
const { id, title, description, due_date, ...metadata } = job;
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
label: job.due_date || "",
|
||||||
|
metadata
|
||||||
|
};
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error while creating board card", error);
|
console.log("Error while creating board card", error);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return boardLanes;
|
return { lanes };
|
||||||
};
|
};
|
||||||
|
|
||||||
const CheckSearch = (search, job) => {
|
const CheckSearch = (search, job) => {
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
|
|
||||||
function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) {
|
function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
console.log("RECORD");
|
||||||
|
console.dir(record);
|
||||||
const [note, setNote] = useState((record.production_vars && record.production_vars.note) || "");
|
const [note, setNote] = useState((record.production_vars && record.production_vars.note) || "");
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export default function ProductionSubletsManageComponent({ subletJobLines }) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updateJobLine] = useMutation(UPDATE_JOB_LINE_SUBLET);
|
const [updateJobLine] = useMutation(UPDATE_JOB_LINE_SUBLET);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
console.log("subletJobLines");
|
||||||
|
console.dir(subletJobLines);
|
||||||
const subletCount = useMemo(() => {
|
const subletCount = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
total: subletJobLines.filter((s) => !s.sublet_ignored).length,
|
total: subletJobLines.filter((s) => !s.sublet_ignored).length,
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { AddCardLink } from "../styles/Base";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const AddCardLinkComponent = ({ onClick, laneId }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return <AddCardLink onClick={onClick}>{t("trello.labels.add_card")}</AddCardLink>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddCardLinkComponent;
|
||||||
112
client/src/components/trello-board/components/Card.jsx
Normal file
112
client/src/components/trello-board/components/Card.jsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import React, { useCallback } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
import { CardHeader, CardRightContent, CardTitle, Detail, Footer, MovableCardWrapper } from "../styles/Base";
|
||||||
|
import InlineInput from "../widgets/InlineInput.jsx";
|
||||||
|
import Tag from "./Card/Tag.jsx";
|
||||||
|
import DeleteButton from "../widgets/DeleteButton.jsx";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const Card = ({
|
||||||
|
showDeleteButton = true,
|
||||||
|
onDelete = () => {},
|
||||||
|
onClick = () => {},
|
||||||
|
style = {},
|
||||||
|
tagStyle = {},
|
||||||
|
className = "",
|
||||||
|
id,
|
||||||
|
title = "no title",
|
||||||
|
label = "",
|
||||||
|
description = "",
|
||||||
|
tags = [],
|
||||||
|
cardDraggable,
|
||||||
|
editable,
|
||||||
|
onChange
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleDelete = useCallback(
|
||||||
|
(e) => {
|
||||||
|
onDelete();
|
||||||
|
e.stopPropagation();
|
||||||
|
},
|
||||||
|
[onDelete]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateCard = (card) => {
|
||||||
|
onChange({ ...card, id });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MovableCardWrapper data-id={id} onClick={onClick} style={style} className={className}>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle draggable={cardDraggable}>
|
||||||
|
{editable ? (
|
||||||
|
<InlineInput
|
||||||
|
value={title}
|
||||||
|
border
|
||||||
|
placeholder={t("trello.labels.title")}
|
||||||
|
resize="vertical"
|
||||||
|
onSave={(value) => updateCard({ title: value })}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
title
|
||||||
|
)}
|
||||||
|
</CardTitle>
|
||||||
|
<CardRightContent>
|
||||||
|
{editable ? (
|
||||||
|
<InlineInput
|
||||||
|
value={label}
|
||||||
|
border
|
||||||
|
placeholder={t("trello.labels.label")}
|
||||||
|
resize="vertical"
|
||||||
|
onSave={(value) => updateCard({ label: value })}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
label
|
||||||
|
)}
|
||||||
|
</CardRightContent>
|
||||||
|
{showDeleteButton && <DeleteButton onClick={handleDelete} />}
|
||||||
|
</CardHeader>
|
||||||
|
<Detail>
|
||||||
|
{editable ? (
|
||||||
|
<InlineInput
|
||||||
|
value={description}
|
||||||
|
border
|
||||||
|
placeholder={t("trello.labels.description")}
|
||||||
|
resize="vertical"
|
||||||
|
onSave={(value) => updateCard({ description: value })}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
description
|
||||||
|
)}
|
||||||
|
</Detail>
|
||||||
|
{tags && tags.length > 0 && (
|
||||||
|
<Footer>
|
||||||
|
{tags.map((tag) => (
|
||||||
|
<Tag key={tag.title} {...tag} tagStyle={tagStyle} />
|
||||||
|
))}
|
||||||
|
</Footer>
|
||||||
|
)}
|
||||||
|
</MovableCardWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Card.propTypes = {
|
||||||
|
showDeleteButton: PropTypes.bool,
|
||||||
|
onDelete: PropTypes.func,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
style: PropTypes.object,
|
||||||
|
tagStyle: PropTypes.object,
|
||||||
|
className: PropTypes.string,
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string,
|
||||||
|
description: PropTypes.string,
|
||||||
|
tags: PropTypes.array,
|
||||||
|
cardDraggable: PropTypes.bool,
|
||||||
|
editable: PropTypes.bool,
|
||||||
|
onChange: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Card;
|
||||||
21
client/src/components/trello-board/components/Card/Tag.jsx
Normal file
21
client/src/components/trello-board/components/Card/Tag.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { TagSpan } from "../../styles/Base";
|
||||||
|
|
||||||
|
const Tag = ({ title, color, bgcolor, tagStyle, ...otherProps }) => {
|
||||||
|
const style = { color: color || "white", backgroundColor: bgcolor || "orange", ...tagStyle };
|
||||||
|
return (
|
||||||
|
<TagSpan style={style} {...otherProps}>
|
||||||
|
{title}
|
||||||
|
</TagSpan>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Tag.propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
color: PropTypes.string,
|
||||||
|
bgcolor: PropTypes.string,
|
||||||
|
tagStyle: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tag;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { LaneFooter } from "../../styles/Base";
|
||||||
|
import { CollapseBtn, ExpandBtn } from "../../styles/Elements";
|
||||||
|
|
||||||
|
const LaneFooterComponent = ({ onClick, collapsed }) => (
|
||||||
|
<LaneFooter onClick={onClick}>{collapsed ? <ExpandBtn /> : <CollapseBtn />}</LaneFooter>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default LaneFooterComponent;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import InlineInput from "../../widgets/InlineInput.jsx";
|
||||||
|
import { LaneHeader, RightContent, Title } from "../../styles/Base";
|
||||||
|
import LaneMenu from "./LaneHeader/LaneMenu.jsx";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const LaneHeaderComponent = ({
|
||||||
|
updateTitle,
|
||||||
|
canAddLanes,
|
||||||
|
onDelete,
|
||||||
|
onDoubleClick,
|
||||||
|
editLaneTitle,
|
||||||
|
label,
|
||||||
|
title,
|
||||||
|
titleStyle,
|
||||||
|
labelStyle,
|
||||||
|
laneDraggable
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LaneHeader onDoubleClick={onDoubleClick} editLaneTitle={editLaneTitle}>
|
||||||
|
<Title draggable={laneDraggable} style={titleStyle}>
|
||||||
|
{editLaneTitle ? (
|
||||||
|
<InlineInput
|
||||||
|
value={title}
|
||||||
|
border
|
||||||
|
placeholder={t("trello.labels.title")}
|
||||||
|
resize="vertical"
|
||||||
|
onSave={updateTitle}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
title
|
||||||
|
)}
|
||||||
|
</Title>
|
||||||
|
{label && (
|
||||||
|
<RightContent>
|
||||||
|
<span style={labelStyle}>{label}</span>
|
||||||
|
</RightContent>
|
||||||
|
)}
|
||||||
|
{canAddLanes && <LaneMenu onDelete={onDelete} />}
|
||||||
|
</LaneHeader>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
LaneHeaderComponent.propTypes = {
|
||||||
|
updateTitle: PropTypes.func,
|
||||||
|
editLaneTitle: PropTypes.bool,
|
||||||
|
canAddLanes: PropTypes.bool,
|
||||||
|
laneDraggable: PropTypes.bool,
|
||||||
|
label: PropTypes.string,
|
||||||
|
title: PropTypes.string,
|
||||||
|
onDelete: PropTypes.func,
|
||||||
|
onDoubleClick: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
LaneHeaderComponent.defaultProps = {
|
||||||
|
updateTitle: () => {},
|
||||||
|
editLaneTitle: false,
|
||||||
|
canAddLanes: false
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LaneHeaderComponent;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { Popover } from "react-popopo";
|
||||||
|
|
||||||
|
import { CustomPopoverContainer, CustomPopoverContent } from "../../../styles/Base";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DeleteWrapper,
|
||||||
|
GenDelButton,
|
||||||
|
LaneMenuContent,
|
||||||
|
LaneMenuHeader,
|
||||||
|
LaneMenuItem,
|
||||||
|
LaneMenuTitle,
|
||||||
|
MenuButton
|
||||||
|
} from "../../../styles/Elements";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const LaneMenu = ({ onDelete }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
position="bottom"
|
||||||
|
PopoverContainer={CustomPopoverContainer}
|
||||||
|
PopoverContent={CustomPopoverContent}
|
||||||
|
trigger={<MenuButton>⋮</MenuButton>}
|
||||||
|
>
|
||||||
|
<LaneMenuHeader>
|
||||||
|
<LaneMenuTitle>{t("trello.labels.lane_actions")}</LaneMenuTitle>
|
||||||
|
<DeleteWrapper>
|
||||||
|
<GenDelButton>✖</GenDelButton>
|
||||||
|
</DeleteWrapper>
|
||||||
|
</LaneMenuHeader>
|
||||||
|
<LaneMenuContent>
|
||||||
|
<LaneMenuItem onClick={onDelete}>{t("trello.labels.delete_lane")}</LaneMenuItem>
|
||||||
|
</LaneMenuContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LaneMenu;
|
||||||
13
client/src/components/trello-board/components/Loader.jsx
Normal file
13
client/src/components/trello-board/components/Loader.jsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {LoaderDiv, LoadingBar} from '../styles/Loader'
|
||||||
|
|
||||||
|
const Loader = () => (
|
||||||
|
<LoaderDiv>
|
||||||
|
<LoadingBar />
|
||||||
|
<LoadingBar />
|
||||||
|
<LoadingBar />
|
||||||
|
<LoadingBar />
|
||||||
|
</LoaderDiv>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Loader
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { CardForm, CardHeader, CardRightContent, CardTitle, CardWrapper, Detail } from "../styles/Base";
|
||||||
|
import { AddButton, CancelButton } from "../styles/Elements";
|
||||||
|
import EditableLabel from "../widgets/EditableLabel.jsx";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const NewCardForm = ({ onCancel, onAdd }) => {
|
||||||
|
const [state, setState] = useState({});
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const updateField = (field, value) => {
|
||||||
|
setState((prevState) => ({ ...prevState, [field]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
onAdd(state);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardForm>
|
||||||
|
<CardWrapper>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>
|
||||||
|
<EditableLabel
|
||||||
|
placeholder={t("trello.labels.title")}
|
||||||
|
onChange={(val) => updateField("title", val)}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</CardTitle>
|
||||||
|
<CardRightContent>
|
||||||
|
<EditableLabel placeholder={t("trello.labels.label")} onChange={(val) => updateField("label", val)} />
|
||||||
|
</CardRightContent>
|
||||||
|
</CardHeader>
|
||||||
|
<Detail>
|
||||||
|
<EditableLabel
|
||||||
|
placeholder={t("trello.labels.description")}
|
||||||
|
onChange={(val) => updateField("description", val)}
|
||||||
|
/>
|
||||||
|
</Detail>
|
||||||
|
</CardWrapper>
|
||||||
|
<AddButton onClick={handleAdd}>{t("trello.labels.add_card")}</AddButton>
|
||||||
|
<CancelButton onClick={onCancel}>{t("trello.labels.cancel")}</CancelButton>
|
||||||
|
</CardForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
NewCardForm.propTypes = {
|
||||||
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
onAdd: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewCardForm;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import React, { useRef } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { LaneTitle, NewLaneButtons, Section } from "../styles/Base";
|
||||||
|
import { AddButton, CancelButton } from "../styles/Elements";
|
||||||
|
import NewLaneTitleEditor from "../widgets/NewLaneTitleEditor.jsx";
|
||||||
|
import { v1 } from "uuid";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const NewLane = ({ onCancel, onAdd }) => {
|
||||||
|
const refInput = useRef(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
onAdd({
|
||||||
|
id: v1(),
|
||||||
|
title: getValue()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getValue = () => refInput.current.getValue();
|
||||||
|
|
||||||
|
const onClickOutside = (a, b, c) => {
|
||||||
|
if (getValue().length > 0) {
|
||||||
|
handleSubmit();
|
||||||
|
} else {
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section>
|
||||||
|
<LaneTitle>
|
||||||
|
<NewLaneTitleEditor
|
||||||
|
ref={refInput}
|
||||||
|
placeholder={t("trello.labels.title")}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onSave={handleSubmit}
|
||||||
|
resize="vertical"
|
||||||
|
border
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</LaneTitle>
|
||||||
|
<NewLaneButtons>
|
||||||
|
<AddButton onClick={handleSubmit}>{t("trello.labels.add_lane")}</AddButton>
|
||||||
|
<CancelButton onClick={onCancel}>{t("trello.labels.cancel")}</CancelButton>
|
||||||
|
</NewLaneButtons>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
NewLane.propTypes = {
|
||||||
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
onAdd: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewLane;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { NewLaneSection } from "../styles/Base";
|
||||||
|
import { AddLaneLink } from "../styles/Elements";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const NewLaneSectionComponent = ({ onClick }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NewLaneSection>
|
||||||
|
<AddLaneLink onClick={onClick}>{t("trello.labels.add_lane")}</AddLaneLink>
|
||||||
|
</NewLaneSection>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewLaneSectionComponent;
|
||||||
24
client/src/components/trello-board/components/index.js
Normal file
24
client/src/components/trello-board/components/index.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import LaneHeader from "./Lane/LaneHeader";
|
||||||
|
import LaneFooter from "./Lane/LaneFooter";
|
||||||
|
import Card from "./Card";
|
||||||
|
import Loader from "./Loader.jsx";
|
||||||
|
import NewLaneForm from "./NewLaneForm.jsx";
|
||||||
|
import NewCardForm from "./NewCardForm.jsx";
|
||||||
|
import AddCardLink from "./AddCardLink";
|
||||||
|
import NewLaneSection from "./NewLaneSection.jsx";
|
||||||
|
import { BoardWrapper, GlobalStyle, ScrollableLane, Section } from "../styles/Base";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
GlobalStyle,
|
||||||
|
BoardWrapper,
|
||||||
|
Loader,
|
||||||
|
ScrollableLane,
|
||||||
|
LaneHeader,
|
||||||
|
LaneFooter,
|
||||||
|
Section,
|
||||||
|
NewLaneForm,
|
||||||
|
NewLaneSection,
|
||||||
|
NewCardForm,
|
||||||
|
Card,
|
||||||
|
AddCardLink
|
||||||
|
};
|
||||||
19
client/src/components/trello-board/controllers/Board.jsx
Normal file
19
client/src/components/trello-board/controllers/Board.jsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { BoardContainer } from "../index.jsx";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { v1 } from "uuid";
|
||||||
|
|
||||||
|
const Board = ({ id, className, components, ...additionalProps }) => {
|
||||||
|
const [storeId] = useState(id || v1());
|
||||||
|
|
||||||
|
const allClassNames = classNames("react-trello-board", className || "");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<components.GlobalStyle />
|
||||||
|
<BoardContainer components={components} {...additionalProps} id={storeId} className={allClassNames} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Board;
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
import { bindActionCreators } from "redux";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import Container from "../dnd/Container";
|
||||||
|
import Draggable from "../dnd/Draggable";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import pick from "lodash/pick";
|
||||||
|
import isEqual from "lodash/isEqual";
|
||||||
|
import Lane from "./Lane";
|
||||||
|
import { PopoverWrapper } from "react-popopo";
|
||||||
|
|
||||||
|
import * as actions from "../../../redux/trello/trello.actions.js";
|
||||||
|
|
||||||
|
class BoardContainer extends Component {
|
||||||
|
state = {
|
||||||
|
addLaneMode: false
|
||||||
|
};
|
||||||
|
|
||||||
|
get groupName() {
|
||||||
|
const { id } = this.props;
|
||||||
|
return `TrelloBoard${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { actions, eventBusHandle } = this.props;
|
||||||
|
actions.loadBoard(this.props.data);
|
||||||
|
if (eventBusHandle) {
|
||||||
|
this.wireEventBus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const { data, reducerData, onDataChange, actions } = this.props;
|
||||||
|
|
||||||
|
if (this.props.reducerData && !isEqual(reducerData, prevProps.reducerData)) {
|
||||||
|
onDataChange(this.props.reducerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && !isEqual(data, prevProps.data)) {
|
||||||
|
actions.loadBoard(data);
|
||||||
|
onDataChange(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragStart = ({ payload }) => {
|
||||||
|
const { handleLaneDragStart } = this.props;
|
||||||
|
handleLaneDragStart(payload.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
onLaneDrop = ({ removedIndex, addedIndex, payload }) => {
|
||||||
|
const { actions, handleLaneDragEnd } = this.props;
|
||||||
|
if (removedIndex !== addedIndex) {
|
||||||
|
actions.moveLane({ oldIndex: removedIndex, newIndex: addedIndex });
|
||||||
|
handleLaneDragEnd(removedIndex, addedIndex, payload);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getCardDetails = (laneId, cardIndex) => {
|
||||||
|
return this.props.reducerData.lanes.find((lane) => lane.id === laneId).cards[cardIndex];
|
||||||
|
};
|
||||||
|
|
||||||
|
getLaneDetails = (index) => {
|
||||||
|
return this.props.reducerData.lanes[index];
|
||||||
|
};
|
||||||
|
|
||||||
|
wireEventBus = () => {
|
||||||
|
const { actions, eventBusHandle } = this.props;
|
||||||
|
let eventBus = {
|
||||||
|
publish: (event) => {
|
||||||
|
switch (event.type) {
|
||||||
|
case "ADD_CARD":
|
||||||
|
return actions.addCard({ laneId: event.laneId, card: event.card });
|
||||||
|
case "UPDATE_CARD":
|
||||||
|
return actions.updateCard({ laneId: event.laneId, card: event.card });
|
||||||
|
case "REMOVE_CARD":
|
||||||
|
return actions.removeCard({ laneId: event.laneId, cardId: event.cardId });
|
||||||
|
case "REFRESH_BOARD":
|
||||||
|
return actions.loadBoard(event.data);
|
||||||
|
case "MOVE_CARD":
|
||||||
|
return actions.moveCardAcrossLanes({
|
||||||
|
fromLaneId: event.fromLaneId,
|
||||||
|
toLaneId: event.toLaneId,
|
||||||
|
cardId: event.cardId,
|
||||||
|
index: event.index
|
||||||
|
});
|
||||||
|
case "UPDATE_CARDS":
|
||||||
|
return actions.updateCards({ laneId: event.laneId, cards: event.cards });
|
||||||
|
case "UPDATE_CARD":
|
||||||
|
return actions.updateCard({ laneId: event.laneId, updatedCard: event.card });
|
||||||
|
case "UPDATE_LANES":
|
||||||
|
return actions.updateLanes(event.lanes);
|
||||||
|
case "UPDATE_LANE":
|
||||||
|
return actions.updateLane(event.lane);
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
eventBusHandle(eventBus);
|
||||||
|
};
|
||||||
|
|
||||||
|
// + add
|
||||||
|
hideEditableLane = () => {
|
||||||
|
this.setState({ addLaneMode: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
showEditableLane = () => {
|
||||||
|
this.setState({ addLaneMode: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
addNewLane = (params) => {
|
||||||
|
this.hideEditableLane();
|
||||||
|
this.props.actions.addLane(params);
|
||||||
|
this.props.onLaneAdd(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
components,
|
||||||
|
reducerData,
|
||||||
|
draggable,
|
||||||
|
laneDraggable,
|
||||||
|
laneDragClass,
|
||||||
|
laneDropClass,
|
||||||
|
style,
|
||||||
|
onDataChange,
|
||||||
|
onCardAdd,
|
||||||
|
onCardUpdate,
|
||||||
|
onCardClick,
|
||||||
|
onBeforeCardDelete,
|
||||||
|
onCardDelete,
|
||||||
|
onLaneScroll,
|
||||||
|
onLaneClick,
|
||||||
|
onLaneAdd,
|
||||||
|
onLaneDelete,
|
||||||
|
onLaneUpdate,
|
||||||
|
editable,
|
||||||
|
canAddLanes,
|
||||||
|
laneStyle,
|
||||||
|
onCardMoveAcrossLanes,
|
||||||
|
t,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { addLaneMode } = this.state;
|
||||||
|
// Stick to whitelisting attributes to segregate board and lane props
|
||||||
|
const passthroughProps = pick(this.props, [
|
||||||
|
"onCardMoveAcrossLanes",
|
||||||
|
"onLaneScroll",
|
||||||
|
"onLaneDelete",
|
||||||
|
"onLaneUpdate",
|
||||||
|
"onCardClick",
|
||||||
|
"onBeforeCardDelete",
|
||||||
|
"onCardDelete",
|
||||||
|
"onCardAdd",
|
||||||
|
"onCardUpdate",
|
||||||
|
"onLaneClick",
|
||||||
|
"laneSortFunction",
|
||||||
|
"draggable",
|
||||||
|
"laneDraggable",
|
||||||
|
"cardDraggable",
|
||||||
|
"collapsibleLanes",
|
||||||
|
"canAddLanes",
|
||||||
|
"hideCardDeleteIcon",
|
||||||
|
"tagStyle",
|
||||||
|
"handleDragStart",
|
||||||
|
"handleDragEnd",
|
||||||
|
"cardDragClass",
|
||||||
|
"editLaneTitle",
|
||||||
|
"t"
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<components.BoardWrapper style={style} {...otherProps} draggable={false}>
|
||||||
|
<PopoverWrapper>
|
||||||
|
<Container
|
||||||
|
orientation="horizontal"
|
||||||
|
onDragStart={this.onDragStart}
|
||||||
|
dragClass={laneDragClass}
|
||||||
|
dropClass={laneDropClass}
|
||||||
|
onDrop={this.onLaneDrop}
|
||||||
|
lockAxis="x"
|
||||||
|
getChildPayload={(index) => this.getLaneDetails(index)}
|
||||||
|
groupName={this.groupName}
|
||||||
|
>
|
||||||
|
{reducerData.lanes.map((lane, index) => {
|
||||||
|
const { id, droppable, ...otherProps } = lane;
|
||||||
|
const laneToRender = (
|
||||||
|
<Lane
|
||||||
|
key={id}
|
||||||
|
boardId={this.groupName}
|
||||||
|
components={components}
|
||||||
|
id={id}
|
||||||
|
getCardDetails={this.getCardDetails}
|
||||||
|
index={index}
|
||||||
|
droppable={droppable === undefined ? true : droppable}
|
||||||
|
style={laneStyle || lane.style || {}}
|
||||||
|
labelStyle={lane.labelStyle || {}}
|
||||||
|
cardStyle={this.props.cardStyle || lane.cardStyle}
|
||||||
|
editable={editable && !lane.disallowAddingCard}
|
||||||
|
{...otherProps}
|
||||||
|
{...passthroughProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return draggable && laneDraggable ? <Draggable key={lane.id}>{laneToRender}</Draggable> : laneToRender;
|
||||||
|
})}
|
||||||
|
</Container>
|
||||||
|
</PopoverWrapper>
|
||||||
|
{canAddLanes && (
|
||||||
|
<Container orientation="horizontal">
|
||||||
|
{editable && !addLaneMode ? (
|
||||||
|
<components.NewLaneSection t={t} onClick={this.showEditableLane} />
|
||||||
|
) : (
|
||||||
|
addLaneMode && <components.NewLaneForm onCancel={this.hideEditableLane} onAdd={this.addNewLane} t={t} />
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
</components.BoardWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BoardContainer.propTypes = {
|
||||||
|
id: PropTypes.string,
|
||||||
|
components: PropTypes.object,
|
||||||
|
actions: PropTypes.object,
|
||||||
|
data: PropTypes.object.isRequired,
|
||||||
|
reducerData: PropTypes.object,
|
||||||
|
onDataChange: PropTypes.func,
|
||||||
|
eventBusHandle: PropTypes.func,
|
||||||
|
onLaneScroll: PropTypes.func,
|
||||||
|
onCardClick: PropTypes.func,
|
||||||
|
onBeforeCardDelete: PropTypes.func,
|
||||||
|
onCardDelete: PropTypes.func,
|
||||||
|
onCardAdd: PropTypes.func,
|
||||||
|
onCardUpdate: PropTypes.func,
|
||||||
|
onLaneAdd: PropTypes.func,
|
||||||
|
onLaneDelete: PropTypes.func,
|
||||||
|
onLaneClick: PropTypes.func,
|
||||||
|
onLaneUpdate: PropTypes.func,
|
||||||
|
laneSortFunction: PropTypes.func,
|
||||||
|
draggable: PropTypes.bool,
|
||||||
|
collapsibleLanes: PropTypes.bool,
|
||||||
|
editable: PropTypes.bool,
|
||||||
|
canAddLanes: PropTypes.bool,
|
||||||
|
hideCardDeleteIcon: PropTypes.bool,
|
||||||
|
handleDragStart: PropTypes.func,
|
||||||
|
handleDragEnd: PropTypes.func,
|
||||||
|
handleLaneDragStart: PropTypes.func,
|
||||||
|
handleLaneDragEnd: PropTypes.func,
|
||||||
|
style: PropTypes.object,
|
||||||
|
tagStyle: PropTypes.object,
|
||||||
|
laneDraggable: PropTypes.bool,
|
||||||
|
cardDraggable: PropTypes.bool,
|
||||||
|
cardDragClass: PropTypes.string,
|
||||||
|
laneDragClass: PropTypes.string,
|
||||||
|
laneDropClass: PropTypes.string,
|
||||||
|
onCardMoveAcrossLanes: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
BoardContainer.defaultProps = {
|
||||||
|
t: (v) => v,
|
||||||
|
onDataChange: () => {},
|
||||||
|
handleDragStart: () => {},
|
||||||
|
handleDragEnd: () => {},
|
||||||
|
handleLaneDragStart: () => {},
|
||||||
|
handleLaneDragEnd: () => {},
|
||||||
|
onCardUpdate: () => {},
|
||||||
|
onLaneAdd: () => {},
|
||||||
|
onLaneDelete: () => {},
|
||||||
|
onCardMoveAcrossLanes: () => {},
|
||||||
|
onLaneUpdate: () => {},
|
||||||
|
editable: false,
|
||||||
|
canAddLanes: false,
|
||||||
|
hideCardDeleteIcon: false,
|
||||||
|
draggable: false,
|
||||||
|
collapsibleLanes: false,
|
||||||
|
laneDraggable: true,
|
||||||
|
cardDraggable: true,
|
||||||
|
cardDragClass: "react_trello_dragClass",
|
||||||
|
laneDragClass: "react_trello_dragLaneClass",
|
||||||
|
laneDropClass: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return state.trello.lanes ? { reducerData: state.trello } : {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
actions: bindActionCreators({ ...actions }, dispatch)
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(BoardContainer);
|
||||||
328
client/src/components/trello-board/controllers/Lane.jsx
Normal file
328
client/src/components/trello-board/controllers/Lane.jsx
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { bindActionCreators } from "redux";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import isEqual from "lodash/isEqual";
|
||||||
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
|
import { v1 } from "uuid";
|
||||||
|
|
||||||
|
import Container from "../dnd/Container.jsx";
|
||||||
|
import Draggable from "../dnd/Draggable.jsx";
|
||||||
|
|
||||||
|
import * as actions from "../../../redux/trello/trello.actions.js";
|
||||||
|
|
||||||
|
class Lane extends Component {
|
||||||
|
state = {
|
||||||
|
loading: false,
|
||||||
|
currentPage: this.props.currentPage,
|
||||||
|
addCardMode: false,
|
||||||
|
collapsed: false,
|
||||||
|
isDraggingOver: false
|
||||||
|
};
|
||||||
|
|
||||||
|
get groupName() {
|
||||||
|
const { boardId } = this.props;
|
||||||
|
return `TrelloBoard${boardId}Lane`;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll = (evt) => {
|
||||||
|
const node = evt.target;
|
||||||
|
const elemScrollPosition = node.scrollHeight - node.scrollTop - node.clientHeight;
|
||||||
|
const { onLaneScroll } = this.props;
|
||||||
|
// In some browsers and/or screen sizes a decimal rest value between 0 and 1 exists, so it should be checked on < 1 instead of < 0
|
||||||
|
if (elemScrollPosition < 1 && onLaneScroll && !this.state.loading) {
|
||||||
|
const { currentPage } = this.state;
|
||||||
|
this.setState({ loading: true });
|
||||||
|
const nextPage = currentPage + 1;
|
||||||
|
onLaneScroll(nextPage, this.props.id).then((moreCards) => {
|
||||||
|
if ((moreCards || []).length > 0) {
|
||||||
|
this.props.actions.paginateLane({
|
||||||
|
laneId: this.props.id,
|
||||||
|
newCards: moreCards,
|
||||||
|
nextPage: nextPage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.setState({ loading: false });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sortCards(cards, sortFunction) {
|
||||||
|
if (!cards) return [];
|
||||||
|
if (!sortFunction) return cards;
|
||||||
|
return cards.concat().sort(function (card1, card2) {
|
||||||
|
return sortFunction(card1, card2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
laneDidMount = (node) => {
|
||||||
|
if (node) {
|
||||||
|
node.addEventListener("scroll", this.handleScroll);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
|
if (!isEqual(this.props.cards, nextProps.cards)) {
|
||||||
|
this.setState({
|
||||||
|
currentPage: nextProps.currentPage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCard = (cardId) => {
|
||||||
|
if (this.props.onBeforeCardDelete && typeof this.props.onBeforeCardDelete === "function") {
|
||||||
|
this.props.onBeforeCardDelete(() => {
|
||||||
|
this.props.onCardDelete && this.props.onCardDelete(cardId, this.props.id);
|
||||||
|
this.props.actions.removeCard({ laneId: this.props.id, cardId: cardId });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.props.onCardDelete && this.props.onCardDelete(cardId, this.props.id);
|
||||||
|
this.props.actions.removeCard({ laneId: this.props.id, cardId: cardId });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCardClick = (e, card) => {
|
||||||
|
const { onCardClick } = this.props;
|
||||||
|
onCardClick && onCardClick(card.id, card.metadata, card.laneId);
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
showEditableCard = () => {
|
||||||
|
this.setState({ addCardMode: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
hideEditableCard = () => {
|
||||||
|
this.setState({ addCardMode: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
addNewCard = (params) => {
|
||||||
|
const laneId = this.props.id;
|
||||||
|
const id = v1();
|
||||||
|
this.hideEditableCard();
|
||||||
|
let card = { id, ...params };
|
||||||
|
this.props.actions.addCard({ laneId, card });
|
||||||
|
this.props.onCardAdd(card, laneId);
|
||||||
|
};
|
||||||
|
|
||||||
|
onDragStart = ({ payload }) => {
|
||||||
|
const { handleDragStart } = this.props;
|
||||||
|
handleDragStart && handleDragStart(payload.id, payload.laneId);
|
||||||
|
};
|
||||||
|
|
||||||
|
shouldAcceptDrop = (sourceContainerOptions) => {
|
||||||
|
return this.props.droppable && sourceContainerOptions.groupName === this.groupName;
|
||||||
|
};
|
||||||
|
|
||||||
|
onDragEnd = (laneId, result) => {
|
||||||
|
const { handleDragEnd } = this.props;
|
||||||
|
const { addedIndex, payload } = result;
|
||||||
|
|
||||||
|
if (this.state.isDraggingOver) {
|
||||||
|
this.setState({ isDraggingOver: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedIndex != null) {
|
||||||
|
const newCard = { ...cloneDeep(payload), laneId };
|
||||||
|
const response = handleDragEnd ? handleDragEnd(payload.id, payload.laneId, laneId, addedIndex, newCard) : true;
|
||||||
|
if (response === undefined || !!response) {
|
||||||
|
this.props.actions.moveCardAcrossLanes({
|
||||||
|
fromLaneId: payload.laneId,
|
||||||
|
toLaneId: laneId,
|
||||||
|
cardId: payload.id,
|
||||||
|
index: addedIndex
|
||||||
|
});
|
||||||
|
this.props.onCardMoveAcrossLanes(payload.laneId, laneId, payload.id, addedIndex);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCard = (updatedCard) => {
|
||||||
|
this.props.actions.updateCard({ laneId: this.props.id, card: updatedCard });
|
||||||
|
this.props.onCardUpdate(this.props.id, updatedCard);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderDragContainer = (isDraggingOver) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
cards,
|
||||||
|
laneSortFunction,
|
||||||
|
editable,
|
||||||
|
hideCardDeleteIcon,
|
||||||
|
cardDraggable,
|
||||||
|
cardDragClass,
|
||||||
|
cardDropClass,
|
||||||
|
tagStyle,
|
||||||
|
cardStyle,
|
||||||
|
components,
|
||||||
|
t
|
||||||
|
} = this.props;
|
||||||
|
const { addCardMode, collapsed } = this.state;
|
||||||
|
|
||||||
|
const showableCards = collapsed ? [] : cards;
|
||||||
|
|
||||||
|
const cardList = this.sortCards(showableCards, laneSortFunction).map((card, idx) => {
|
||||||
|
const onDeleteCard = () => this.removeCard(card.id);
|
||||||
|
const cardToRender = (
|
||||||
|
<components.Card
|
||||||
|
key={card.id}
|
||||||
|
index={idx}
|
||||||
|
style={card.style || cardStyle}
|
||||||
|
className="react-trello-card"
|
||||||
|
onDelete={onDeleteCard}
|
||||||
|
onClick={(e) => this.handleCardClick(e, card)}
|
||||||
|
onChange={(updatedCard) => this.updateCard(updatedCard)}
|
||||||
|
showDeleteButton={!hideCardDeleteIcon}
|
||||||
|
tagStyle={tagStyle}
|
||||||
|
cardDraggable={cardDraggable}
|
||||||
|
editable={editable}
|
||||||
|
{...card}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return cardDraggable && (!card.hasOwnProperty("draggable") || card.draggable) ? (
|
||||||
|
<Draggable key={card.id}>{cardToRender}</Draggable>
|
||||||
|
) : (
|
||||||
|
<span key={card.id}>{cardToRender}</span>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<components.ScrollableLane ref={this.laneDidMount} isDraggingOver={isDraggingOver}>
|
||||||
|
<Container
|
||||||
|
orientation="vertical"
|
||||||
|
groupName={this.groupName}
|
||||||
|
dragClass={cardDragClass}
|
||||||
|
dropClass={cardDropClass}
|
||||||
|
onDragStart={this.onDragStart}
|
||||||
|
onDrop={(e) => this.onDragEnd(id, e)}
|
||||||
|
onDragEnter={() => this.setState({ isDraggingOver: true })}
|
||||||
|
onDragLeave={() => this.setState({ isDraggingOver: false })}
|
||||||
|
shouldAcceptDrop={this.shouldAcceptDrop}
|
||||||
|
getChildPayload={(index) => this.props.getCardDetails(id, index)}
|
||||||
|
>
|
||||||
|
{cardList}
|
||||||
|
</Container>
|
||||||
|
{editable && !addCardMode && <components.AddCardLink onClick={this.showEditableCard} laneId={id} />}
|
||||||
|
{addCardMode && <components.NewCardForm onCancel={this.hideEditableCard} laneId={id} onAdd={this.addNewCard} />}
|
||||||
|
</components.ScrollableLane>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
removeLane = () => {
|
||||||
|
const { id } = this.props;
|
||||||
|
this.props.actions.removeLane({ laneId: id });
|
||||||
|
this.props.onLaneDelete(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateTitle = (value) => {
|
||||||
|
this.props.actions.updateLane({ id: this.props.id, title: value });
|
||||||
|
this.props.onLaneUpdate(this.props.id, { title: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
renderHeader = (pickedProps) => {
|
||||||
|
const { components } = this.props;
|
||||||
|
return (
|
||||||
|
<components.LaneHeader
|
||||||
|
{...pickedProps}
|
||||||
|
onDelete={this.removeLane}
|
||||||
|
onDoubleClick={this.toggleLaneCollapsed}
|
||||||
|
updateTitle={this.updateTitle}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleLaneCollapsed = () => {
|
||||||
|
this.props.collapsibleLanes && this.setState((state) => ({ collapsed: !state.collapsed }));
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, isDraggingOver, collapsed } = this.state;
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
cards,
|
||||||
|
collapsibleLanes,
|
||||||
|
components,
|
||||||
|
onLaneClick,
|
||||||
|
onLaneScroll,
|
||||||
|
onCardClick,
|
||||||
|
onCardAdd,
|
||||||
|
onBeforeCardDelete,
|
||||||
|
onCardDelete,
|
||||||
|
onLaneDelete,
|
||||||
|
onLaneUpdate,
|
||||||
|
onCardUpdate,
|
||||||
|
onCardMoveAcrossLanes,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
const allClassNames = classNames("react-trello-lane", this.props.className || "");
|
||||||
|
const showFooter = collapsibleLanes && cards.length > 0;
|
||||||
|
return (
|
||||||
|
<components.Section
|
||||||
|
{...otherProps}
|
||||||
|
key={id}
|
||||||
|
onClick={() => onLaneClick && onLaneClick(id)}
|
||||||
|
draggable={false}
|
||||||
|
className={allClassNames}
|
||||||
|
>
|
||||||
|
{this.renderHeader({ id, cards, ...otherProps })}
|
||||||
|
{this.renderDragContainer(isDraggingOver)}
|
||||||
|
{loading && <components.Loader />}
|
||||||
|
{showFooter && <components.LaneFooter onClick={this.toggleLaneCollapsed} collapsed={collapsed} />}
|
||||||
|
</components.Section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Lane.propTypes = {
|
||||||
|
actions: PropTypes.object,
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
boardId: PropTypes.string,
|
||||||
|
title: PropTypes.node,
|
||||||
|
index: PropTypes.number,
|
||||||
|
laneSortFunction: PropTypes.func,
|
||||||
|
style: PropTypes.object,
|
||||||
|
cardStyle: PropTypes.object,
|
||||||
|
tagStyle: PropTypes.object,
|
||||||
|
titleStyle: PropTypes.object,
|
||||||
|
labelStyle: PropTypes.object,
|
||||||
|
cards: PropTypes.array,
|
||||||
|
label: PropTypes.string,
|
||||||
|
currentPage: PropTypes.number,
|
||||||
|
draggable: PropTypes.bool,
|
||||||
|
collapsibleLanes: PropTypes.bool,
|
||||||
|
droppable: PropTypes.bool,
|
||||||
|
onCardMoveAcrossLanes: PropTypes.func,
|
||||||
|
onCardClick: PropTypes.func,
|
||||||
|
onBeforeCardDelete: PropTypes.func,
|
||||||
|
onCardDelete: PropTypes.func,
|
||||||
|
onCardAdd: PropTypes.func,
|
||||||
|
onCardUpdate: PropTypes.func,
|
||||||
|
onLaneDelete: PropTypes.func,
|
||||||
|
onLaneUpdate: PropTypes.func,
|
||||||
|
onLaneClick: PropTypes.func,
|
||||||
|
onLaneScroll: PropTypes.func,
|
||||||
|
editable: PropTypes.bool,
|
||||||
|
laneDraggable: PropTypes.bool,
|
||||||
|
cardDraggable: PropTypes.bool,
|
||||||
|
cardDragClass: PropTypes.string,
|
||||||
|
cardDropClass: PropTypes.string,
|
||||||
|
canAddLanes: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
Lane.defaultProps = {
|
||||||
|
style: {},
|
||||||
|
titleStyle: {},
|
||||||
|
labelStyle: {},
|
||||||
|
label: undefined,
|
||||||
|
editable: false,
|
||||||
|
onLaneUpdate: () => {},
|
||||||
|
onCardAdd: () => {},
|
||||||
|
onCardUpdate: () => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
actions: bindActionCreators(actions, dispatch)
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(Lane);
|
||||||
139
client/src/components/trello-board/dnd/Container.jsx
Normal file
139
client/src/components/trello-board/dnd/Container.jsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import React, {Component} from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import container, {dropHandlers} from 'kuika-smooth-dnd'
|
||||||
|
|
||||||
|
container.dropHandler = dropHandlers.reactDropHandler().handler
|
||||||
|
container.wrapChild = p => p // dont wrap children they will already be wrapped
|
||||||
|
|
||||||
|
class Container extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.getContainerOptions = this.getContainerOptions.bind(this)
|
||||||
|
this.setRef = this.setRef.bind(this)
|
||||||
|
this.prevContainer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.containerDiv = this.containerDiv || ReactDOM.findDOMNode(this)
|
||||||
|
this.prevContainer = this.containerDiv
|
||||||
|
this.container = container(this.containerDiv, this.getContainerOptions())
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.container.dispose()
|
||||||
|
this.container = null
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.containerDiv = this.containerDiv || ReactDOM.findDOMNode(this)
|
||||||
|
if (this.containerDiv) {
|
||||||
|
if (this.prevContainer && this.prevContainer !== this.containerDiv) {
|
||||||
|
this.container.dispose()
|
||||||
|
this.container = container(this.containerDiv, this.getContainerOptions())
|
||||||
|
this.prevContainer = this.containerDiv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.props.render) {
|
||||||
|
return this.props.render(this.setRef)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div style={this.props.style} ref={this.setRef}>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setRef(element) {
|
||||||
|
this.containerDiv = element
|
||||||
|
}
|
||||||
|
|
||||||
|
getContainerOptions() {
|
||||||
|
const functionProps = {}
|
||||||
|
|
||||||
|
if (this.props.onDragStart) {
|
||||||
|
functionProps.onDragStart = (...p) => this.props.onDragStart(...p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.onDragEnd) {
|
||||||
|
functionProps.onDragEnd = (...p) => this.props.onDragEnd(...p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.onDrop) {
|
||||||
|
functionProps.onDrop = (...p) => this.props.onDrop(...p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.getChildPayload) {
|
||||||
|
functionProps.getChildPayload = (...p) => this.props.getChildPayload(...p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.shouldAnimateDrop) {
|
||||||
|
functionProps.shouldAnimateDrop = (...p) => this.props.shouldAnimateDrop(...p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.shouldAcceptDrop) {
|
||||||
|
functionProps.shouldAcceptDrop = (...p) => this.props.shouldAcceptDrop(...p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.onDragEnter) {
|
||||||
|
functionProps.onDragEnter = (...p) => this.props.onDragEnter(...p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.onDragLeave) {
|
||||||
|
functionProps.onDragLeave = (...p) => this.props.onDragLeave(...p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.render) {
|
||||||
|
functionProps.render = (...p) => this.props.render(...p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.onDropReady) {
|
||||||
|
functionProps.onDropReady = (...p) => this.props.onDropReady(...p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.getGhostParent) {
|
||||||
|
functionProps.getGhostParent = (...p) => this.props.getGhostParent(...p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign({}, this.props, functionProps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Container.propTypes = {
|
||||||
|
behaviour: PropTypes.oneOf(['move', 'copy', 'drag-zone']),
|
||||||
|
groupName: PropTypes.string,
|
||||||
|
orientation: PropTypes.oneOf(['horizontal', 'vertical']),
|
||||||
|
style: PropTypes.object,
|
||||||
|
dragHandleSelector: PropTypes.string,
|
||||||
|
className: PropTypes.string,
|
||||||
|
nonDragAreaSelector: PropTypes.string,
|
||||||
|
dragBeginDelay: PropTypes.number,
|
||||||
|
animationDuration: PropTypes.number,
|
||||||
|
autoScrollEnabled: PropTypes.string,
|
||||||
|
lockAxis: PropTypes.string,
|
||||||
|
dragClass: PropTypes.string,
|
||||||
|
dropClass: PropTypes.string,
|
||||||
|
onDragStart: PropTypes.func,
|
||||||
|
onDragEnd: PropTypes.func,
|
||||||
|
onDrop: PropTypes.func,
|
||||||
|
getChildPayload: PropTypes.func,
|
||||||
|
shouldAnimateDrop: PropTypes.func,
|
||||||
|
shouldAcceptDrop: PropTypes.func,
|
||||||
|
onDragEnter: PropTypes.func,
|
||||||
|
onDragLeave: PropTypes.func,
|
||||||
|
render: PropTypes.func,
|
||||||
|
getGhostParent: PropTypes.func,
|
||||||
|
removeOnDropOut: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
Container.defaultProps = {
|
||||||
|
behaviour: 'move',
|
||||||
|
orientation: 'vertical',
|
||||||
|
className: 'reactTrelloBoard'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Container
|
||||||
26
client/src/components/trello-board/dnd/Draggable.jsx
Normal file
26
client/src/components/trello-board/dnd/Draggable.jsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React, {Component} from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import {constants} from 'kuika-smooth-dnd'
|
||||||
|
|
||||||
|
const {wrapperClass} = constants
|
||||||
|
|
||||||
|
class Draggable extends Component {
|
||||||
|
render() {
|
||||||
|
if (this.props.render) {
|
||||||
|
return React.cloneElement(this.props.render(), {className: wrapperClass})
|
||||||
|
}
|
||||||
|
|
||||||
|
const clsName = `${this.props.className ? this.props.className + ' ' : ''}`
|
||||||
|
return (
|
||||||
|
<div {...this.props} className={`${clsName}${wrapperClass}`}>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Draggable.propTypes = {
|
||||||
|
render: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Draggable
|
||||||
135
client/src/components/trello-board/helpers/LaneHelper.js
Normal file
135
client/src/components/trello-board/helpers/LaneHelper.js
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import update from "immutability-helper";
|
||||||
|
|
||||||
|
const LaneHelper = {
|
||||||
|
initialiseLanes: (state, { lanes }) => {
|
||||||
|
const newLanes = lanes.map((lane) => {
|
||||||
|
lane.currentPage = 1;
|
||||||
|
lane.cards && lane.cards.forEach((c) => (c.laneId = lane.id));
|
||||||
|
return lane;
|
||||||
|
});
|
||||||
|
return update(state, { lanes: { $set: newLanes } });
|
||||||
|
},
|
||||||
|
|
||||||
|
paginateLane: (state, { laneId, newCards, nextPage }) => {
|
||||||
|
const updatedLanes = LaneHelper.appendCardsToLane(state, { laneId: laneId, newCards: newCards });
|
||||||
|
updatedLanes.find((lane) => lane.id === laneId).currentPage = nextPage;
|
||||||
|
return update(state, { lanes: { $set: updatedLanes } });
|
||||||
|
},
|
||||||
|
|
||||||
|
appendCardsToLane: (state, { laneId, newCards, index }) => {
|
||||||
|
const lane = state.lanes.find((lane) => lane.id === laneId);
|
||||||
|
newCards = newCards
|
||||||
|
.map((c) => update(c, { laneId: { $set: laneId } }))
|
||||||
|
.filter((c) => lane.cards.find((card) => card.id === c.id) == null);
|
||||||
|
return state.lanes.map((lane) => {
|
||||||
|
if (lane.id === laneId) {
|
||||||
|
if (index !== undefined) {
|
||||||
|
return update(lane, { cards: { $splice: [[index, 0, ...newCards]] } });
|
||||||
|
} else {
|
||||||
|
const cardsToUpdate = [...lane.cards, ...newCards];
|
||||||
|
return update(lane, { cards: { $set: cardsToUpdate } });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return lane;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
appendCardToLane: (state, { laneId, card, index }) => {
|
||||||
|
const newLanes = LaneHelper.appendCardsToLane(state, { laneId: laneId, newCards: [card], index });
|
||||||
|
return update(state, { lanes: { $set: newLanes } });
|
||||||
|
},
|
||||||
|
|
||||||
|
addLane: (state, lane) => {
|
||||||
|
const newLane = { cards: [], ...lane };
|
||||||
|
return update(state, { lanes: { $push: [newLane] } });
|
||||||
|
},
|
||||||
|
|
||||||
|
updateLane: (state, updatedLane) => {
|
||||||
|
const newLanes = state.lanes.map((lane) => {
|
||||||
|
if (updatedLane.id === lane.id) {
|
||||||
|
return { ...lane, ...updatedLane };
|
||||||
|
} else {
|
||||||
|
return lane;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return update(state, { lanes: { $set: newLanes } });
|
||||||
|
},
|
||||||
|
|
||||||
|
removeCardFromLane: (state, { laneId, cardId }) => {
|
||||||
|
const lanes = state.lanes.map((lane) => {
|
||||||
|
if (lane.id === laneId) {
|
||||||
|
let newCards = lane.cards.filter((card) => card.id !== cardId);
|
||||||
|
return update(lane, { cards: { $set: newCards } });
|
||||||
|
} else {
|
||||||
|
return lane;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return update(state, { lanes: { $set: lanes } });
|
||||||
|
},
|
||||||
|
|
||||||
|
moveCardAcrossLanes: (state, { fromLaneId, toLaneId, cardId, index }) => {
|
||||||
|
let cardToMove = null;
|
||||||
|
const interimLanes = state.lanes.map((lane) => {
|
||||||
|
if (lane.id === fromLaneId) {
|
||||||
|
cardToMove = lane.cards.find((card) => card.id === cardId);
|
||||||
|
const newCards = lane.cards.filter((card) => card.id !== cardId);
|
||||||
|
return update(lane, { cards: { $set: newCards } });
|
||||||
|
} else {
|
||||||
|
return lane;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const updatedState = update(state, { lanes: { $set: interimLanes } });
|
||||||
|
return LaneHelper.appendCardToLane(updatedState, {
|
||||||
|
laneId: toLaneId,
|
||||||
|
card: cardToMove,
|
||||||
|
index: index
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCardsForLane: (state, { laneId, cards }) => {
|
||||||
|
const lanes = state.lanes.map((lane) => {
|
||||||
|
if (lane.id === laneId) {
|
||||||
|
return update(lane, { cards: { $set: cards } });
|
||||||
|
} else {
|
||||||
|
return lane;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return update(state, { lanes: { $set: lanes } });
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCardForLane: (state, { laneId, card: updatedCard }) => {
|
||||||
|
const lanes = state.lanes.map((lane) => {
|
||||||
|
if (lane.id === laneId) {
|
||||||
|
const cards = lane.cards.map((card) => {
|
||||||
|
if (card.id === updatedCard.id) {
|
||||||
|
return { ...card, ...updatedCard };
|
||||||
|
} else {
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return update(lane, { cards: { $set: cards } });
|
||||||
|
} else {
|
||||||
|
return lane;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return update(state, { lanes: { $set: lanes } });
|
||||||
|
},
|
||||||
|
|
||||||
|
updateLanes: (state, lanes) => {
|
||||||
|
return { ...state, ...{ lanes: lanes } };
|
||||||
|
},
|
||||||
|
|
||||||
|
moveLane: (state, { oldIndex, newIndex }) => {
|
||||||
|
const laneToMove = state.lanes[oldIndex];
|
||||||
|
const tempState = update(state, { lanes: { $splice: [[oldIndex, 1]] } });
|
||||||
|
return update(tempState, { lanes: { $splice: [[newIndex, 0, laneToMove]] } });
|
||||||
|
},
|
||||||
|
|
||||||
|
removeLane: (state, { laneId }) => {
|
||||||
|
const updatedLanes = state.lanes.filter((lane) => lane.id !== laneId);
|
||||||
|
return update(state, { lanes: { $set: updatedLanes } });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LaneHelper;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
const REPLACE_TABLE = {
|
||||||
|
customLaneHeader: 'components.LaneHeader',
|
||||||
|
newLaneTemplate: 'components.NewLaneSection',
|
||||||
|
newCardTemplate: 'components.NewCardForm',
|
||||||
|
children: 'components.Card',
|
||||||
|
customCardLayout: 'components.Card',
|
||||||
|
addLaneTitle: '`t` function with key "Add another lane"',
|
||||||
|
addCardLink: '`t` function with key "Click to add card"'
|
||||||
|
}
|
||||||
|
|
||||||
|
const warn = prop => {
|
||||||
|
const use = REPLACE_TABLE[prop]
|
||||||
|
console.warn(
|
||||||
|
`react-trello property '${prop}' is removed. Use '${use}' instead. More - https://github.com/rcdexta/react-trello/blob/master/UPGRADE.md`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default props => {
|
||||||
|
Object.keys(REPLACE_TABLE).forEach(key => {
|
||||||
|
if (props.hasOwnProperty(key)) {
|
||||||
|
warn(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
38
client/src/components/trello-board/index.jsx
Normal file
38
client/src/components/trello-board/index.jsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import Draggable from "./dnd/Draggable.jsx";
|
||||||
|
import Container from "./dnd/Container.jsx";
|
||||||
|
import BoardContainer from "./controllers/BoardContainer.jsx";
|
||||||
|
import Board from "./controllers/Board.jsx";
|
||||||
|
import Lane from "./controllers/Lane.jsx";
|
||||||
|
import deprecationWarnings from "./helpers/deprecationWarnings";
|
||||||
|
import DefaultComponents from "./components";
|
||||||
|
|
||||||
|
import widgets from "./widgets/index";
|
||||||
|
|
||||||
|
import { StyleSheetManager } from "styled-components";
|
||||||
|
import isPropValid from "@emotion/is-prop-valid";
|
||||||
|
|
||||||
|
export { Draggable, Container, BoardContainer, Lane, widgets };
|
||||||
|
|
||||||
|
export { DefaultComponents as components };
|
||||||
|
|
||||||
|
// Enhanced default export using arrow function for simplicity
|
||||||
|
const TrelloBoard = ({ components, ...otherProps }) => {
|
||||||
|
deprecationWarnings(otherProps);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyleSheetManager shouldForwardProp={shouldForwardProp}>
|
||||||
|
<Board components={{ ...DefaultComponents, ...components }} {...otherProps} />;
|
||||||
|
</StyleSheetManager>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldForwardProp = (propName, target) => {
|
||||||
|
if (typeof target === "string") {
|
||||||
|
return isPropValid(propName);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TrelloBoard;
|
||||||
298
client/src/components/trello-board/styles/Base.js
Normal file
298
client/src/components/trello-board/styles/Base.js
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
import { PopoverContainer, PopoverContent } from "react-popopo";
|
||||||
|
import styled, { createGlobalStyle, css } from "styled-components";
|
||||||
|
|
||||||
|
export const GlobalStyle = createGlobalStyle`
|
||||||
|
.comPlainTextContentEditable {
|
||||||
|
-webkit-user-modify: read-write-plaintext-only;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comPlainTextContentEditable--has-placeholder::before {
|
||||||
|
content: attr(placeholder);
|
||||||
|
opacity: 0.5;
|
||||||
|
color: inherit;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react_trello_dragClass {
|
||||||
|
transform: rotate(3deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.react_trello_dragLaneClass {
|
||||||
|
transform: rotate(3deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-overflow-menu-horizontal:before {
|
||||||
|
content: "\\E91F";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-lg, .icon-sm {
|
||||||
|
color: #798d99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-lg {
|
||||||
|
height: 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CustomPopoverContainer = styled(PopoverContainer)`
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CustomPopoverContent = styled(PopoverContent)`
|
||||||
|
visibility: hidden;
|
||||||
|
margin-top: -5px;
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
transition: all 0.3s ease 0ms;
|
||||||
|
border-radius: 3px;
|
||||||
|
min-width: 7em;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #000;
|
||||||
|
padding: 5px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.active &&
|
||||||
|
`
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
transition-delay: 100ms;
|
||||||
|
`} &::before {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgba(255, 255, 255, 0.56);
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
margin: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #00bcd4 !important;
|
||||||
|
color: #37474f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const BoardWrapper = styled.div`
|
||||||
|
background-color: #3179ba;
|
||||||
|
overflow-y: hidden;
|
||||||
|
padding: 5px;
|
||||||
|
color: #393939;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
height: 100vh;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Header = styled.header`
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Section = styled.section`
|
||||||
|
background-color: #e3e3e3;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 5px 5px;
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
display: inline-flex;
|
||||||
|
height: auto;
|
||||||
|
max-height: 90%;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LaneHeader = styled(Header)`
|
||||||
|
margin-bottom: 0px;
|
||||||
|
${(props) =>
|
||||||
|
props.editLaneTitle &&
|
||||||
|
css`
|
||||||
|
padding: 0px;
|
||||||
|
line-height: 30px;
|
||||||
|
`} ${(props) =>
|
||||||
|
!props.editLaneTitle &&
|
||||||
|
css`
|
||||||
|
padding: 0px 5px;
|
||||||
|
`};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LaneFooter = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
height: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ScrollableLane = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-width: 250px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
align-self: center;
|
||||||
|
max-height: 90vh;
|
||||||
|
min-height: 100px;
|
||||||
|
margin-top: 10px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Title = styled.span`
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 18px;
|
||||||
|
cursor: ${(props) => (props.draggable ? "grab" : `auto`)};
|
||||||
|
width: 70%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RightContent = styled.span`
|
||||||
|
width: 38%;
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
`;
|
||||||
|
export const CardWrapper = styled.article`
|
||||||
|
border-radius: 3px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
max-width: 250px;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
min-width: 230px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const MovableCardWrapper = styled(CardWrapper)`
|
||||||
|
&:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CardHeader = styled(Header)`
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
color: #000;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CardTitle = styled(Title)`
|
||||||
|
font-size: 14px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CardRightContent = styled(RightContent)`
|
||||||
|
font-size: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Detail = styled.div`
|
||||||
|
font-size: 12px;
|
||||||
|
color: #4d4d4d;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Footer = styled.div`
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
padding-top: 6px;
|
||||||
|
text-align: right;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TagSpan = styled.span`
|
||||||
|
padding: 2px 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 2px 5px;
|
||||||
|
font-size: 70%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const AddCardLink = styled.a`
|
||||||
|
border-radius: 0 0 3px 3px;
|
||||||
|
color: #838c91;
|
||||||
|
display: block;
|
||||||
|
padding: 5px 2px;
|
||||||
|
margin-top: 10px;
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
//background-color: #cdd2d4;
|
||||||
|
color: #4d4d4d;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LaneTitle = styled.div`
|
||||||
|
font-size: 15px;
|
||||||
|
width: 268px;
|
||||||
|
height: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LaneSection = styled.section`
|
||||||
|
background-color: #2b6aa3;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 5px;
|
||||||
|
position: relative;
|
||||||
|
padding: 5px;
|
||||||
|
display: inline-flex;
|
||||||
|
height: auto;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const NewLaneSection = styled(LaneSection)`
|
||||||
|
width: 200px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const NewLaneButtons = styled.div`
|
||||||
|
margin-top: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CardForm = styled.div`
|
||||||
|
background-color: #e3e3e3;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const InlineInput = styled.textarea`
|
||||||
|
overflow-x: hidden; /* for Firefox (issue #5) */
|
||||||
|
word-wrap: break-word;
|
||||||
|
min-height: 18px;
|
||||||
|
max-height: 112px; /* optional, but recommended */
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 18px;
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
text-align: inherit;
|
||||||
|
background-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 0;
|
||||||
|
padding: 0 8px;
|
||||||
|
outline: 0;
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.border &&
|
||||||
|
css`
|
||||||
|
&:focus {
|
||||||
|
box-shadow: inset 0 0 0 2px #0079bf;
|
||||||
|
}
|
||||||
|
`} &:focus {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
`;
|
||||||
251
client/src/components/trello-board/styles/Elements.js
Normal file
251
client/src/components/trello-board/styles/Elements.js
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
import {CardWrapper, MovableCardWrapper} from './Base'
|
||||||
|
|
||||||
|
export const DeleteWrapper = styled.div`
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
right: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const GenDelButton = styled.button`
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
display: inline-block;
|
||||||
|
border: none;
|
||||||
|
font-size: 15px;
|
||||||
|
height: 15px;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
text-align: center;
|
||||||
|
width: 15px;
|
||||||
|
background: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const DelButton = styled.button`
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
display: inline-block;
|
||||||
|
border: none;
|
||||||
|
font-size: 8px;
|
||||||
|
height: 15px;
|
||||||
|
line-height: 1px;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 15px;
|
||||||
|
background: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
${MovableCardWrapper}:hover & {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const MenuButton = styled.button`
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
display: inline-block;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
height: 15px;
|
||||||
|
line-height: 1px;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 15px;
|
||||||
|
background: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const LaneMenuHeader = styled.div`
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
text-align: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const LaneMenuContent = styled.div`
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0 12px 12px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const LaneMenuItem = styled.div`
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 6px 12px;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 -12px;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #3179ba;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const LaneMenuTitle = styled.span`
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #6b808c;
|
||||||
|
display: block;
|
||||||
|
line-height: 30px;
|
||||||
|
border-bottom: 1px solid rgba(9, 45, 66, 0.13);
|
||||||
|
margin: 0 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0 32px;
|
||||||
|
position: relative;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const DeleteIcon = styled.span`
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
opacity: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #83bd42;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 4px;
|
||||||
|
background-color: #83bd42;
|
||||||
|
|
||||||
|
${CardWrapper}:hover & {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover::before,
|
||||||
|
&:hover::after {
|
||||||
|
background: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 2px;
|
||||||
|
width: 60%;
|
||||||
|
top: 45%;
|
||||||
|
left: 20%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
-moz-transform: rotate(45deg);
|
||||||
|
-o-transform: rotate(45deg);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
-webkit-transform: rotate(-45deg);
|
||||||
|
-moz-transform: rotate(-45deg);
|
||||||
|
-o-transform: rotate(-45deg);
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ExpandCollapseBase = styled.span`
|
||||||
|
width: 36px;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: 14px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const CollapseBtn = styled(ExpandCollapseBase)`
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-bottom: 7px solid #444;
|
||||||
|
border-left: 7px solid transparent;
|
||||||
|
border-right: 7px solid transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 4px;
|
||||||
|
top: 4px;
|
||||||
|
border-bottom: 3px solid #e3e3e3;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
border-right: 3px solid transparent;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ExpandBtn = styled(ExpandCollapseBase)`
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-top: 7px solid #444;
|
||||||
|
border-left: 7px solid transparent;
|
||||||
|
border-right: 7px solid transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 4px;
|
||||||
|
top: 0px;
|
||||||
|
border-top: 3px solid #e3e3e3;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
border-right: 3px solid transparent;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const AddButton = styled.button`
|
||||||
|
background: #5aac44;
|
||||||
|
color: #fff;
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 4px 16px;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-right: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 0;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const CancelButton = styled.button`
|
||||||
|
background: #999999;
|
||||||
|
color: #fff;
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 4px 16px;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-top: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 0;
|
||||||
|
`
|
||||||
|
export const AddLaneLink = styled.button`
|
||||||
|
background: #2b6aa3;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 4px 16px;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-right: 0px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 0;
|
||||||
|
`
|
||||||
43
client/src/components/trello-board/styles/Loader.js
Normal file
43
client/src/components/trello-board/styles/Loader.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import styled, {keyframes} from 'styled-components'
|
||||||
|
|
||||||
|
const keyframeAnimation = keyframes`
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
transform: scale(1, 2.2);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const LoaderDiv = styled.div`
|
||||||
|
text-align: center;
|
||||||
|
margin: 15px 0;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const LoadingBar = styled.div`
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 2px;
|
||||||
|
width: 4px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 4px;
|
||||||
|
animation: ${keyframeAnimation} 1s ease-in-out infinite;
|
||||||
|
background-color: #777;
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
animation-delay: 0.0001s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
animation-delay: 0.09s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
animation-delay: 0.18s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(4) {
|
||||||
|
animation-delay: 0.27s;
|
||||||
|
}
|
||||||
|
`
|
||||||
12
client/src/components/trello-board/widgets/DeleteButton.jsx
Normal file
12
client/src/components/trello-board/widgets/DeleteButton.jsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { DelButton, DeleteWrapper } from "../styles/Elements";
|
||||||
|
|
||||||
|
const DeleteButton = (props) => {
|
||||||
|
return (
|
||||||
|
<DeleteWrapper {...props}>
|
||||||
|
<DelButton>✖</DelButton>
|
||||||
|
</DeleteWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteButton;
|
||||||
87
client/src/components/trello-board/widgets/EditableLabel.jsx
Normal file
87
client/src/components/trello-board/widgets/EditableLabel.jsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
class EditableLabel extends React.Component {
|
||||||
|
constructor({value}) {
|
||||||
|
super()
|
||||||
|
this.state = {value: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
getText = el => {
|
||||||
|
return el.innerText
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextChange = ev => {
|
||||||
|
const value = this.getText(ev.target)
|
||||||
|
this.setState({value: value})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.autoFocus) {
|
||||||
|
this.refDiv.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlur = () => {
|
||||||
|
this.props.onChange(this.state.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
onPaste = ev => {
|
||||||
|
ev.preventDefault()
|
||||||
|
const value = ev.clipboardData.getData('text')
|
||||||
|
document.execCommand('insertText', false, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
getClassName = () => {
|
||||||
|
const placeholder = this.state.value === '' ? 'comPlainTextContentEditable--has-placeholder' : ''
|
||||||
|
return `comPlainTextContentEditable ${placeholder}`
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown = e => {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
this.props.onChange(this.state.value)
|
||||||
|
this.refDiv.blur()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
this.refDiv.value = this.props.value
|
||||||
|
this.setState({value: this.props.value})
|
||||||
|
// this.refDiv.blur()
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const placeholder = this.props.value.length > 0 ? false : this.props.placeholder
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref => (this.refDiv = ref)}
|
||||||
|
contentEditable="true"
|
||||||
|
className={this.getClassName()}
|
||||||
|
onPaste={this.onPaste}
|
||||||
|
onBlur={this.onBlur}
|
||||||
|
onInput={this.onTextChange}
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditableLabel.propTypes = {
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
autoFocus: PropTypes.bool,
|
||||||
|
inline: PropTypes.bool,
|
||||||
|
value: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
EditableLabel.defaultProps = {
|
||||||
|
onChange: () => {},
|
||||||
|
placeholder: '',
|
||||||
|
autoFocus: false,
|
||||||
|
inline: false,
|
||||||
|
value: ''
|
||||||
|
}
|
||||||
|
export default EditableLabel
|
||||||
106
client/src/components/trello-board/widgets/InlineInput.jsx
Normal file
106
client/src/components/trello-board/widgets/InlineInput.jsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { InlineInput } from "../styles/Base";
|
||||||
|
import autosize from "autosize";
|
||||||
|
|
||||||
|
const InlineInputController = ({ onSave, border, placeholder, value, autoFocus, resize, onCancel }) => {
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
const [inputValue, setInputValue] = useState(value);
|
||||||
|
|
||||||
|
// Effect for autosizing and initial autoFocus
|
||||||
|
useEffect(() => {
|
||||||
|
if (inputRef.current && resize !== "none") {
|
||||||
|
autosize(inputRef.current);
|
||||||
|
}
|
||||||
|
if (inputRef.current && autoFocus) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [resize, autoFocus]);
|
||||||
|
|
||||||
|
// Effect to update value when props change
|
||||||
|
useEffect(() => {
|
||||||
|
setInputValue(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
const handleFocus = (e) => e.target.select();
|
||||||
|
|
||||||
|
const handleMouseDown = (e) => {
|
||||||
|
if (document.activeElement !== e.target) {
|
||||||
|
e.preventDefault();
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
updateValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
// Enter
|
||||||
|
inputRef.current.blur();
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (e.keyCode === 27) {
|
||||||
|
// Escape
|
||||||
|
setInputValue(value); // Reset to initial value
|
||||||
|
inputRef.current.blur();
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (e.keyCode === 9) {
|
||||||
|
// Tab
|
||||||
|
if (inputValue.length === 0) {
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
inputRef.current.blur();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateValue = () => {
|
||||||
|
if (inputValue !== value) {
|
||||||
|
onSave(inputValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InlineInput
|
||||||
|
ref={inputRef}
|
||||||
|
border={border}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder={inputValue.length === 0 ? undefined : placeholder}
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
dataGramm="false"
|
||||||
|
rows={1}
|
||||||
|
autoFocus={autoFocus}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
InlineInputController.propTypes = {
|
||||||
|
onSave: PropTypes.func,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
border: PropTypes.bool,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
value: PropTypes.string,
|
||||||
|
autoFocus: PropTypes.bool,
|
||||||
|
resize: PropTypes.oneOf(["none", "vertical", "horizontal"])
|
||||||
|
};
|
||||||
|
|
||||||
|
InlineInputController.defaultProps = {
|
||||||
|
onSave: () => {},
|
||||||
|
onCancel: () => {},
|
||||||
|
placeholder: "",
|
||||||
|
value: "",
|
||||||
|
border: false,
|
||||||
|
autoFocus: false,
|
||||||
|
resize: "none"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InlineInputController;
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { InlineInput } from "../styles/Base";
|
||||||
|
import autosize from "autosize";
|
||||||
|
|
||||||
|
class NewLaneTitleEditor extends React.Component {
|
||||||
|
onKeyDown = (e) => {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
this.refInput.blur();
|
||||||
|
this.props.onSave();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
this.cancel();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 9) {
|
||||||
|
if (this.getValue().length === 0) {
|
||||||
|
this.cancel();
|
||||||
|
} else {
|
||||||
|
this.props.onSave();
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cancel = () => {
|
||||||
|
this.setValue("");
|
||||||
|
this.props.onCancel();
|
||||||
|
this.refInput.blur();
|
||||||
|
};
|
||||||
|
|
||||||
|
getValue = () => this.refInput.value;
|
||||||
|
setValue = (value) => (this.refInput.value = value);
|
||||||
|
|
||||||
|
saveValue = () => {
|
||||||
|
if (this.getValue() !== this.props.value) {
|
||||||
|
this.props.onSave(this.getValue());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
focus = () => this.refInput.focus();
|
||||||
|
|
||||||
|
setRef = (ref) => {
|
||||||
|
this.refInput = ref;
|
||||||
|
if (this.props.resize !== "none") {
|
||||||
|
autosize(this.refInput);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { autoFocus, resize, border, autoResize, value, placeholder } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InlineInput
|
||||||
|
style={{ resize: resize }}
|
||||||
|
ref={this.setRef}
|
||||||
|
border={border}
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
|
placeholder={value.length === 0 ? undefined : placeholder}
|
||||||
|
defaultValue={value}
|
||||||
|
rows={3}
|
||||||
|
autoResize={autoResize}
|
||||||
|
autoFocus={autoFocus}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NewLaneTitleEditor.propTypes = {
|
||||||
|
onSave: PropTypes.func,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
border: PropTypes.bool,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
value: PropTypes.string,
|
||||||
|
autoFocus: PropTypes.bool,
|
||||||
|
autoResize: PropTypes.bool,
|
||||||
|
resize: PropTypes.oneOf(["none", "vertical", "horizontal"])
|
||||||
|
};
|
||||||
|
|
||||||
|
NewLaneTitleEditor.defaultProps = {
|
||||||
|
inputRef: () => {},
|
||||||
|
onSave: () => {},
|
||||||
|
onCancel: () => {},
|
||||||
|
placeholder: "",
|
||||||
|
value: "",
|
||||||
|
border: false,
|
||||||
|
autoFocus: false,
|
||||||
|
autoResize: false,
|
||||||
|
resize: "none"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewLaneTitleEditor;
|
||||||
9
client/src/components/trello-board/widgets/index.jsx
Normal file
9
client/src/components/trello-board/widgets/index.jsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import DeleteButton from "./DeleteButton";
|
||||||
|
import EditableLabel from "./EditableLabel";
|
||||||
|
import InlineInput from "./InlineInput";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
DeleteButton,
|
||||||
|
EditableLabel,
|
||||||
|
InlineInput
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ import messagingReducer from "./messaging/messaging.reducer";
|
|||||||
import modalsReducer from "./modals/modals.reducer";
|
import modalsReducer from "./modals/modals.reducer";
|
||||||
import techReducer from "./tech/tech.reducer";
|
import techReducer from "./tech/tech.reducer";
|
||||||
import userReducer from "./user/user.reducer";
|
import userReducer from "./user/user.reducer";
|
||||||
|
import trelloReducer from "./trello/trello.reducer";
|
||||||
|
|
||||||
// const persistConfig = {
|
// const persistConfig = {
|
||||||
// key: "root",
|
// key: "root",
|
||||||
@@ -30,11 +31,8 @@ const rootReducer = combineReducers({
|
|||||||
modals: modalsReducer,
|
modals: modalsReducer,
|
||||||
application: persistReducer(applicationPersistConfig, applicationReducer),
|
application: persistReducer(applicationPersistConfig, applicationReducer),
|
||||||
tech: techReducer,
|
tech: techReducer,
|
||||||
media: mediaReducer
|
media: mediaReducer,
|
||||||
|
trello: trelloReducer
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withReduxStateSync(
|
export default withReduxStateSync(rootReducer);
|
||||||
// persistReducer(persistConfig,
|
|
||||||
rootReducer
|
|
||||||
//)
|
|
||||||
);
|
|
||||||
|
|||||||
14
client/src/redux/trello/trello.actions.js
Normal file
14
client/src/redux/trello/trello.actions.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { createAction } from "redux-actions";
|
||||||
|
|
||||||
|
export const loadBoard = createAction("LOAD_BOARD");
|
||||||
|
export const addLane = createAction("ADD_LANE");
|
||||||
|
export const addCard = createAction("ADD_CARD");
|
||||||
|
export const updateCard = createAction("UPDATE_CARD");
|
||||||
|
export const removeCard = createAction("REMOVE_CARD");
|
||||||
|
export const moveCardAcrossLanes = createAction("MOVE_CARD");
|
||||||
|
export const updateCards = createAction("UPDATE_CARDS");
|
||||||
|
export const updateLanes = createAction("UPDATE_LANES");
|
||||||
|
export const updateLane = createAction("UPDATE_LANE");
|
||||||
|
export const paginateLane = createAction("PAGINATE_LANE");
|
||||||
|
export const moveLane = createAction("MOVE_LANE");
|
||||||
|
export const removeLane = createAction("REMOVE_LANE");
|
||||||
35
client/src/redux/trello/trello.reducer.js
Normal file
35
client/src/redux/trello/trello.reducer.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import Lh from "../../components/trello-board/helpers/LaneHelper";
|
||||||
|
|
||||||
|
const boardReducer = (state = { lanes: [] }, action) => {
|
||||||
|
const { payload, type } = action;
|
||||||
|
switch (type) {
|
||||||
|
case "LOAD_BOARD":
|
||||||
|
return Lh.initialiseLanes(state, payload);
|
||||||
|
case "ADD_CARD":
|
||||||
|
return Lh.appendCardToLane(state, payload);
|
||||||
|
case "REMOVE_CARD":
|
||||||
|
return Lh.removeCardFromLane(state, payload);
|
||||||
|
case "MOVE_CARD":
|
||||||
|
return Lh.moveCardAcrossLanes(state, payload);
|
||||||
|
case "UPDATE_CARDS":
|
||||||
|
return Lh.updateCardsForLane(state, payload);
|
||||||
|
case "UPDATE_CARD":
|
||||||
|
return Lh.updateCardForLane(state, payload);
|
||||||
|
case "UPDATE_LANES":
|
||||||
|
return Lh.updateLanes(state, payload);
|
||||||
|
case "UPDATE_LANE":
|
||||||
|
return Lh.updateLane(state, payload);
|
||||||
|
case "PAGINATE_LANE":
|
||||||
|
return Lh.paginateLane(state, payload);
|
||||||
|
case "MOVE_LANE":
|
||||||
|
return Lh.moveLane(state, payload);
|
||||||
|
case "REMOVE_LANE":
|
||||||
|
return Lh.removeLane(state, payload);
|
||||||
|
case "ADD_LANE":
|
||||||
|
return Lh.addLane(state, payload);
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default boardReducer;
|
||||||
@@ -3460,6 +3460,18 @@
|
|||||||
"validation": {
|
"validation": {
|
||||||
"unique_vendor_name": "You must enter a unique vendor name."
|
"unique_vendor_name": "You must enter a unique vendor name."
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"trello": {
|
||||||
|
"labels": {
|
||||||
|
"add_card": "Add Card",
|
||||||
|
"add_lane": "Add Lane",
|
||||||
|
"delete_lane": "Delete Lane",
|
||||||
|
"lane_actions": "Lane Actions",
|
||||||
|
"title": "Title",
|
||||||
|
"description": "Description",
|
||||||
|
"label": "Label",
|
||||||
|
"cancel": "Cancel"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3460,6 +3460,18 @@
|
|||||||
"validation": {
|
"validation": {
|
||||||
"unique_vendor_name": ""
|
"unique_vendor_name": ""
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"trello": {
|
||||||
|
"labels": {
|
||||||
|
"add_card": "",
|
||||||
|
"add_lane": "",
|
||||||
|
"delete_lane": "",
|
||||||
|
"lane_actions": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"label": "",
|
||||||
|
"cancel": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3460,6 +3460,18 @@
|
|||||||
"validation": {
|
"validation": {
|
||||||
"unique_vendor_name": ""
|
"unique_vendor_name": ""
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"trello": {
|
||||||
|
"labels": {
|
||||||
|
"add_card": "",
|
||||||
|
"add_lane": "",
|
||||||
|
"delete_lane": "",
|
||||||
|
"lane_actions": "",
|
||||||
|
"title": "",
|
||||||
|
"description": "",
|
||||||
|
"label": "",
|
||||||
|
"cancel": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as path from "path";
|
|||||||
import * as url from "url";
|
import * as url from "url";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import { ViteEjsPlugin } from "vite-plugin-ejs";
|
import { ViteEjsPlugin } from "vite-plugin-ejs";
|
||||||
import eslint from 'vite-plugin-eslint';
|
import eslint from "vite-plugin-eslint";
|
||||||
|
|
||||||
//import CompressionPlugin from 'vite-plugin-compression';
|
//import CompressionPlugin from 'vite-plugin-compression';
|
||||||
import { VitePWA } from "vite-plugin-pwa";
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
@@ -103,7 +103,7 @@ export default defineConfig({
|
|||||||
}),
|
}),
|
||||||
reactVirtualized(),
|
reactVirtualized(),
|
||||||
react(),
|
react(),
|
||||||
eslint(),
|
eslint()
|
||||||
// CompressionPlugin(), //Cloudfront already compresses assets, so not needed.
|
// CompressionPlugin(), //Cloudfront already compresses assets, so not needed.
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
|
|||||||
Reference in New Issue
Block a user